@h-rig/core 0.0.6-alpha.14 → 0.0.6-alpha.140

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
@@ -43,11 +43,76 @@ function definePlugin(meta, runtime) {
43
43
  }
44
44
  }
45
45
  }
46
+ const declaredHooks = new Map((validated.contributes?.hooks ?? []).map((h) => [h.id, h]));
47
+ for (const hookId of Object.keys(runtime.hooks ?? {})) {
48
+ const metadata = declaredHooks.get(hookId);
49
+ if (!metadata) {
50
+ throw new Error(`definePlugin(${validated.name}): typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
51
+ }
52
+ if (metadata.command) {
53
+ throw new Error(`definePlugin(${validated.name}): hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
54
+ }
55
+ }
56
+ if (runtime.hooks) {
57
+ for (const h of declaredHooks.values()) {
58
+ if (!runtime.hooks[h.id] && !h.command) {
59
+ throw new Error(`definePlugin(${validated.name}): hook metadata "${h.id}" has no implementation (typed function or command)`);
60
+ }
61
+ }
62
+ }
63
+ const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
64
+ for (const capability of runtime.featureCapabilities ?? []) {
65
+ if (!declaredCapabilities.has(capability.id)) {
66
+ throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
67
+ }
68
+ }
69
+ const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
70
+ for (const panel of runtime.panels ?? []) {
71
+ const metadata = declaredPanels.get(panel.id);
72
+ if (!metadata) {
73
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
74
+ }
75
+ if (metadata.slot !== panel.slot) {
76
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
77
+ }
78
+ if (metadata.capabilityId !== panel.capabilityId) {
79
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
80
+ }
81
+ }
82
+ const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
83
+ for (const classifier of runtime.blockerClassifiers ?? []) {
84
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
85
+ throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
86
+ }
87
+ }
88
+ const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
89
+ for (const command of runtime.cliCommands ?? []) {
90
+ if (!declaredCliCommands.has(command.id)) {
91
+ throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
92
+ }
93
+ }
46
94
  return { ...validated, __runtime: runtime };
47
95
  }
48
96
  // packages/core/src/define-config.ts
49
97
  import { Schema as Schema2 } from "effect";
50
98
  import { RigConfig } from "@rig/contracts";
99
+ function normalizeWorkspaceConfig(raw) {
100
+ const workspace = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : { mainRepo: "." };
101
+ workspace.checkout = workspace.checkout ?? workspace.isolation ?? "worktree";
102
+ workspace.isolation = workspace.isolation ?? workspace.checkout;
103
+ workspace.sandbox = workspace.sandbox ?? "enforce";
104
+ return workspace;
105
+ }
106
+ function applyConfigDefaults(raw) {
107
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
108
+ return raw;
109
+ const record = raw;
110
+ return {
111
+ ...record,
112
+ plugins: Array.isArray(record.plugins) ? record.plugins : [],
113
+ workspace: normalizeWorkspaceConfig(record.workspace)
114
+ };
115
+ }
51
116
  function defineConfig(cfg) {
52
117
  const runtimeByName = new Map;
53
118
  const plugins = cfg.plugins ?? [];
@@ -56,7 +121,7 @@ function defineConfig(cfg) {
56
121
  runtimeByName.set(plugin.name, plugin.__runtime);
57
122
  }
58
123
  }
59
- const decoded = Schema2.decodeUnknownSync(RigConfig)(cfg);
124
+ const decoded = Schema2.decodeUnknownSync(RigConfig)(applyConfigDefaults(cfg));
60
125
  const decodedPlugins = decoded.plugins.map((p) => {
61
126
  const runtime = runtimeByName.get(p.name);
62
127
  if (!runtime)
@@ -124,6 +189,59 @@ function assertRuntimeMatchesMetadata(plugin) {
124
189
  }
125
190
  }
126
191
  }
192
+ const declaredHooks = new Map((plugin.contributes?.hooks ?? []).map((hook) => [hook.id, hook]));
193
+ const runtimeHooks = plugin.__runtime?.hooks;
194
+ for (const hookId of Object.keys(runtimeHooks ?? {})) {
195
+ const metadata = declaredHooks.get(hookId);
196
+ if (!metadata) {
197
+ throw new Error(`plugin "${plugin.name}" typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
198
+ }
199
+ if (metadata.command) {
200
+ throw new Error(`plugin "${plugin.name}" hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
201
+ }
202
+ }
203
+ if (runtimeHooks) {
204
+ for (const hook of declaredHooks.values()) {
205
+ if (!runtimeHooks[hook.id] && !hook.command) {
206
+ throw new Error(`plugin "${plugin.name}" hook metadata "${hook.id}" has no implementation (typed function or command)`);
207
+ }
208
+ }
209
+ }
210
+ const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
211
+ const runtimeCapabilities = new Map((plugin.__runtime?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
212
+ for (const capability of runtimeCapabilities.values()) {
213
+ if (!declaredCapabilities.has(capability.id)) {
214
+ throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
215
+ }
216
+ }
217
+ const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
218
+ const runtimePanels = new Map((plugin.__runtime?.panels ?? []).map((panel) => [panel.id, panel]));
219
+ for (const panel of runtimePanels.values()) {
220
+ const metadata = declaredPanels.get(panel.id);
221
+ if (!metadata) {
222
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
223
+ }
224
+ if (metadata.slot !== panel.slot) {
225
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
226
+ }
227
+ if (metadata.capabilityId !== panel.capabilityId) {
228
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
229
+ }
230
+ }
231
+ const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
232
+ const runtimeBlockerClassifiers = new Map((plugin.__runtime?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
233
+ for (const classifier of runtimeBlockerClassifiers.values()) {
234
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
235
+ throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
236
+ }
237
+ }
238
+ const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
239
+ const runtimeCliCommands = new Map((plugin.__runtime?.cliCommands ?? []).map((command) => [command.id, command]));
240
+ for (const command of runtimeCliCommands.values()) {
241
+ if (!declaredCliCommands.has(command.id)) {
242
+ throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
243
+ }
244
+ }
127
245
  }
128
246
  function createPluginHost(plugins) {
129
247
  assertUniquePluginNames(plugins);
@@ -138,8 +256,18 @@ function createPluginHost(plugins) {
138
256
  const taskFieldExtensions = [];
139
257
  const taskSources = [];
140
258
  const cliCommands = [];
259
+ const capabilities = [];
260
+ const panels = [];
261
+ const blockerClassifiers = [];
262
+ const stages = [];
263
+ const stageMutations = [];
264
+ const stageExecutors = {};
141
265
  const executableValidators = [];
142
266
  const executableTaskSources = [];
267
+ const executableCapabilities = [];
268
+ const executablePanels = [];
269
+ const executableBlockerClassifiers = [];
270
+ const executableCliCommands = [];
143
271
  for (const plugin of plugins) {
144
272
  const c = plugin.contributes;
145
273
  if (!c)
@@ -151,6 +279,18 @@ function createPluginHost(plugins) {
151
279
  if (plugin.__runtime?.taskSources) {
152
280
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
153
281
  }
282
+ if (plugin.__runtime?.featureCapabilities) {
283
+ executableCapabilities.push(...plugin.__runtime.featureCapabilities.map((item) => ({ item, pluginName })));
284
+ }
285
+ if (plugin.__runtime?.panels) {
286
+ executablePanels.push(...plugin.__runtime.panels.map((item) => ({ item, pluginName })));
287
+ }
288
+ if (plugin.__runtime?.blockerClassifiers) {
289
+ executableBlockerClassifiers.push(...plugin.__runtime.blockerClassifiers.map((item) => ({ item, pluginName })));
290
+ }
291
+ if (plugin.__runtime?.cliCommands) {
292
+ executableCliCommands.push(...plugin.__runtime.cliCommands.map((item) => ({ item, pluginName })));
293
+ }
154
294
  if (c.validators)
155
295
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
156
296
  if (c.hooks)
@@ -167,9 +307,25 @@ function createPluginHost(plugins) {
167
307
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
168
308
  if (c.cliCommands)
169
309
  cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
310
+ if (c.capabilities)
311
+ capabilities.push(...c.capabilities.map((item) => ({ item, pluginName })));
312
+ if (c.panels)
313
+ panels.push(...c.panels.map((item) => ({ item, pluginName })));
314
+ if (c.blockerClassifiers)
315
+ blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
316
+ if (c.stages)
317
+ stages.push(...c.stages.map((item) => ({ item, pluginName })));
318
+ if (c.stageMutations)
319
+ stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
320
+ if (plugin.__runtime?.stages)
321
+ Object.assign(stageExecutors, plugin.__runtime.stages);
170
322
  }
171
323
  indexById(executableValidators, "executableValidator");
172
324
  indexById(executableTaskSources, "executableTaskSource");
325
+ indexById(executableCapabilities, "executableCapability");
326
+ indexById(executablePanels, "executablePanel");
327
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
328
+ indexById(executableCliCommands, "executableCliCommand");
173
329
  const taskSourceFactoryByKind = new Map;
174
330
  const taskSourceKindRegistrant = new Map;
175
331
  for (const { item, pluginName } of executableTaskSources) {
@@ -187,6 +343,9 @@ function createPluginHost(plugins) {
187
343
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
188
344
  const taskSourceMap = indexById(taskSources, "taskSource");
189
345
  const cliCommandMap = indexById(cliCommands, "cliCommand");
346
+ const capabilityMap = indexById(capabilities, "capability");
347
+ const panelMap = indexById(panels, "panel");
348
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
190
349
  const allValidators = validators.map((c) => c.item);
191
350
  const allHooks = hooks.map((c) => c.item);
192
351
  const allSkills = skills.map((c) => c.item);
@@ -195,8 +354,34 @@ function createPluginHost(plugins) {
195
354
  const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
196
355
  const allTaskSources = taskSources.map((c) => c.item);
197
356
  const allCliCommands = cliCommands.map((c) => c.item);
357
+ const allStageMutations = stageMutations.map((c) => c.item);
358
+ const allStages = stages.map((c) => c.item);
359
+ const allCapabilities = capabilities.map((c) => c.item);
360
+ const allPanels = panels.map((c) => c.item);
361
+ const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
198
362
  const allExecutableValidators = executableValidators.map((c) => c.item);
199
363
  const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
364
+ const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
365
+ const allExecutablePanels = executablePanels.map((c) => c.item);
366
+ const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
367
+ const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
368
+ const executableCliCommandByName = new Map;
369
+ const registerExecutableCliCommandSelector = (selector, contribution) => {
370
+ const existing = executableCliCommandByName.get(selector);
371
+ if (existing && existing.item.id !== contribution.item.id) {
372
+ throw new Error(`duplicate executable CLI selector "${selector}" registered by command "${existing.item.id}" from plugin "${existing.pluginName}" and command "${contribution.item.id}" from plugin "${contribution.pluginName}"`);
373
+ }
374
+ executableCliCommandByName.set(selector, contribution);
375
+ };
376
+ for (const contribution of executableCliCommands) {
377
+ const command = contribution.item;
378
+ const family = command.family ?? command.id;
379
+ registerExecutableCliCommandSelector(command.id, contribution);
380
+ registerExecutableCliCommandSelector(family, contribution);
381
+ for (const alias of command.aliases ?? []) {
382
+ registerExecutableCliCommandSelector(alias, contribution);
383
+ }
384
+ }
200
385
  return {
201
386
  getValidator: (id) => validatorMap.get(id),
202
387
  getHook: (id) => hookMap.get(id),
@@ -206,6 +391,9 @@ function createPluginHost(plugins) {
206
391
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
207
392
  getTaskSource: (id) => taskSourceMap.get(id),
208
393
  getCliCommand: (id) => cliCommandMap.get(id),
394
+ getCapability: (id) => capabilityMap.get(id),
395
+ getPanel: (id) => panelMap.get(id),
396
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
209
397
  listValidators: () => allValidators,
210
398
  listHooks: () => allHooks,
211
399
  listSkills: () => allSkills,
@@ -214,30 +402,44 @@ function createPluginHost(plugins) {
214
402
  listTaskFieldExtensions: () => allTaskFieldExtensions,
215
403
  listTaskSources: () => allTaskSources,
216
404
  listCliCommands: () => allCliCommands,
405
+ listCapabilities: () => allCapabilities,
406
+ listPanels: () => allPanels,
407
+ listBlockerClassifiers: () => allBlockerClassifiers,
408
+ listStages: () => allStages,
409
+ listStageMutations: () => allStageMutations,
410
+ listStageExecutors: () => stageExecutors,
217
411
  listExecutableValidators: () => allExecutableValidators,
218
412
  listExecutableTaskSources: () => allExecutableTaskSources,
413
+ listExecutableCapabilities: () => allExecutableCapabilities,
414
+ listExecutablePanels: () => allExecutablePanels,
415
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
416
+ listExecutableCliCommands: () => allExecutableCliCommands,
417
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
219
418
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
220
419
  };
221
420
  }
222
421
  // packages/core/src/rig-init-builder.ts
223
422
  function buildRigInitConfigSource(input) {
224
423
  const lines = [`import { defineConfig } from "@rig/core";`];
225
- if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
226
- lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
227
- } else if (input.useStandardPlugin) {
228
- lines.push(`import standard from "@rig/standard-plugin";`);
424
+ if (input.useStandardPlugin) {
425
+ lines.push(`import { standardProjectPlugins } from "@rig/standard-plugin/bundle";`);
426
+ if (input.taskSource.kind === "github-issues") {
427
+ lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
428
+ }
229
429
  }
230
430
  lines.push(``, `export default defineConfig({`);
231
431
  const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
232
432
  lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
233
433
  if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
234
- lines.push(` plugins: [standard({`);
235
- lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
236
- lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
237
- lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
434
+ lines.push(` plugins: [...standardProjectPlugins({`);
435
+ lines.push(` standard: {`);
436
+ lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
437
+ lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
438
+ lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
439
+ lines.push(` },`);
238
440
  lines.push(` })],`);
239
441
  } else {
240
- lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
442
+ lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
241
443
  }
242
444
  if (input.taskSource.kind === "github-issues") {
243
445
  lines.push(` taskSource: {`);
@@ -257,7 +459,8 @@ function buildRigInitConfigSource(input) {
257
459
  lines.push(` },`);
258
460
  }
259
461
  lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
260
- lines.push(` runtime: { harness: "pi", mode: "yolo" },`);
462
+ const sshTarget = input.sshTarget?.trim();
463
+ lines.push(sshTarget ? ` runtime: { harness: "pi", mode: "yolo", server: { sshTarget: ${JSON.stringify(sshTarget)} } },` : ` runtime: { harness: "pi", mode: "yolo" }, // server.sshTarget unset = local placement`);
261
464
  lines.push(` planning: { mode: "auto" },`);
262
465
  lines.push(` github: {`);
263
466
  lines.push(` issueUpdates: "lifecycle",`);
@@ -283,6 +486,7 @@ import {
283
486
  WorkspaceId,
284
487
  WorktreeId
285
488
  } from "@rig/contracts";
489
+ var CANONICAL_RUNTIME_ADAPTER = "pi";
286
490
  function isRecord(value) {
287
491
  return typeof value === "object" && value !== null;
288
492
  }
@@ -422,7 +626,7 @@ function mapLegacySessionStatusToRunStatus(status) {
422
626
  case "error":
423
627
  return "failed";
424
628
  case "stopped":
425
- return "cancelled";
629
+ return "stopped";
426
630
  default:
427
631
  return "created";
428
632
  }
@@ -458,12 +662,15 @@ function mapTaskStatusFromRunStatus(status, fallback) {
458
662
  case "paused":
459
663
  return "in_progress";
460
664
  case "reviewing":
665
+ case "closing-out":
461
666
  return "under_review";
667
+ case "needs-attention":
668
+ return "blocked";
462
669
  case "completed":
463
670
  return "completed";
464
671
  case "failed":
465
672
  return "ready";
466
- case "cancelled":
673
+ case "stopped":
467
674
  return "cancelled";
468
675
  }
469
676
  }
@@ -647,7 +854,7 @@ function applySyntheticRuntimePreparation(snapshot, event) {
647
854
  ...existingRun,
648
855
  taskId: existingRun.taskId ?? nextTaskId,
649
856
  runKind: existingRun.runKind === "adhoc" && nextTaskId ? "task" : existingRun.runKind,
650
- runtimeAdapter: "rig-native",
857
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
651
858
  initialPrompt: existingRun.initialPrompt ?? null,
652
859
  executionTarget: existingRun.executionTarget ?? "local",
653
860
  remoteHostId: existingRun.remoteHostId ?? null,
@@ -663,7 +870,7 @@ function applySyntheticRuntimePreparation(snapshot, event) {
663
870
  ...runtimeBase,
664
871
  workspaceId: nextWorkspaceId,
665
872
  runId: runtimeRunId,
666
- adapterKind: "rig-native",
873
+ adapterKind: CANONICAL_RUNTIME_ADAPTER,
667
874
  executionTarget: existingRun.executionTarget ?? "local",
668
875
  remoteHostId: existingRun.remoteHostId ?? null,
669
876
  status: failed ? "failed" : finished ? "prepared" : "starting",
@@ -712,7 +919,7 @@ function applySyntheticRuntimePrepared(snapshot, event) {
712
919
  ...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(existingRun, event.createdAt),
713
920
  workspaceId: nextWorkspaceId,
714
921
  runId: runtimeRunId,
715
- adapterKind: "rig-native",
922
+ adapterKind: CANONICAL_RUNTIME_ADAPTER,
716
923
  executionTarget: existingRun.executionTarget ?? "local",
717
924
  remoteHostId: existingRun.remoteHostId ?? null,
718
925
  status: "prepared",
@@ -755,7 +962,7 @@ function applyLegacyProjectEvent(snapshot, event) {
755
962
  title,
756
963
  rootPath,
757
964
  sourceKind: "native",
758
- defaultRuntimeAdapter: "codex-app-server",
965
+ defaultRuntimeAdapter: CANONICAL_RUNTIME_ADAPTER,
759
966
  defaultModel: readNullableString(payload, "defaultModel") ?? null,
760
967
  createdAt,
761
968
  updatedAt
@@ -850,7 +1057,7 @@ function applyLegacyThreadEvent(snapshot, event) {
850
1057
  runtimeMode: "full-access",
851
1058
  interactionMode: "default",
852
1059
  status: "created",
853
- runtimeAdapter: "codex-app-server",
1060
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
854
1061
  model,
855
1062
  initialPrompt: null,
856
1063
  activeRuntimeId: null,
@@ -1034,7 +1241,7 @@ function applyLegacyThreadEvent(snapshot, event) {
1034
1241
  const providerName = readNullableString(session, "providerName");
1035
1242
  const nextRun = {
1036
1243
  ...existingRun,
1037
- runtimeAdapter: providerName ?? existingRun.runtimeAdapter,
1244
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
1038
1245
  initialPrompt: existingRun.initialPrompt ?? null,
1039
1246
  activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
1040
1247
  status: mapLegacySessionStatusToRunStatus(readString(session, "status")),
@@ -1047,7 +1254,7 @@ function applyLegacyThreadEvent(snapshot, event) {
1047
1254
  ...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, sessionUpdatedAt),
1048
1255
  workspaceId: existingRun.workspaceId,
1049
1256
  runId: asRunId(runId),
1050
- adapterKind: providerName ?? existingRun.runtimeAdapter,
1257
+ adapterKind: CANONICAL_RUNTIME_ADAPTER,
1051
1258
  status: mapLegacySessionStatusToRuntimeStatus(readString(session, "status")),
1052
1259
  workspaceDir: existingRun.worktreePath,
1053
1260
  isolationMode: existingRun.worktreePath ? "worktree" : "env",
@@ -1281,7 +1488,7 @@ function applyEngineEvent(snapshot, event) {
1281
1488
  runtimeMode: payload.runtimeMode,
1282
1489
  interactionMode: payload.interactionMode,
1283
1490
  status: "created",
1284
- runtimeAdapter: payload.runtimeAdapter,
1491
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
1285
1492
  model: payload.model ?? null,
1286
1493
  initialPrompt: payload.initialPrompt ?? null,
1287
1494
  executionTarget: payload.executionTarget ?? (payload.remoteHostId ? "remote" : "local"),
@@ -1313,7 +1520,7 @@ function applyEngineEvent(snapshot, event) {
1313
1520
  ...base,
1314
1521
  runs: patchById(base.runs, payload.runId, (run) => ({
1315
1522
  ...run,
1316
- status: "cancelled",
1523
+ status: "stopped",
1317
1524
  updatedAt: payload.createdAt,
1318
1525
  completedAt: payload.createdAt
1319
1526
  }))
@@ -1977,7 +2184,7 @@ function applyEngineEvent(snapshot, event) {
1977
2184
  runtimeMode: "full-access",
1978
2185
  interactionMode: "default",
1979
2186
  status: "created",
1980
- runtimeAdapter: "rig-native",
2187
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
1981
2188
  model: null,
1982
2189
  initialPrompt: null,
1983
2190
  activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
@@ -1997,7 +2204,7 @@ function applyEngineEvent(snapshot, event) {
1997
2204
  workspaceId: asWorkspaceId(workspaceId),
1998
2205
  taskId: asTaskId(taskId),
1999
2206
  runKind: "task",
2000
- runtimeAdapter: "rig-native",
2207
+ runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
2001
2208
  activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
2002
2209
  updatedAt: event.createdAt
2003
2210
  });
@@ -2085,10 +2292,40 @@ function projectTaskStatusForGrouping(status) {
2085
2292
  case "in_progress":
2086
2293
  case "under_review":
2087
2294
  return "running";
2295
+ case null:
2296
+ case undefined:
2297
+ case "":
2298
+ return "unknown";
2088
2299
  default:
2089
2300
  return status;
2090
2301
  }
2091
2302
  }
2303
+ function projectRunStatusForTaskGrouping(status) {
2304
+ switch (status) {
2305
+ case "created":
2306
+ case "queued":
2307
+ case "preparing":
2308
+ return "queued";
2309
+ case "running":
2310
+ case "waiting-approval":
2311
+ case "waiting-user-input":
2312
+ case "paused":
2313
+ case "validating":
2314
+ case "reviewing":
2315
+ case "closing-out":
2316
+ return "running";
2317
+ case "needs-attention":
2318
+ return "blocked";
2319
+ case "failed":
2320
+ case "stopped":
2321
+ return "ready";
2322
+ case "completed":
2323
+ return "completed";
2324
+ case null:
2325
+ case undefined:
2326
+ return null;
2327
+ }
2328
+ }
2092
2329
  var STATUS_PRIORITY = [
2093
2330
  "running",
2094
2331
  "blocked",
@@ -2100,11 +2337,31 @@ var STATUS_PRIORITY = [
2100
2337
  "cancelled",
2101
2338
  "completed"
2102
2339
  ];
2103
- function selectTasksGroupedByStatus(snapshot, workspaceId) {
2104
- const tasks = selectTasksByWorkspace(snapshot, workspaceId);
2340
+ function taskIdFromSession(session) {
2341
+ return session.taskId ?? session.record.taskId ?? null;
2342
+ }
2343
+ function latestSessionByTask(sessions) {
2344
+ const byTask = new Map;
2345
+ for (const session of sessions) {
2346
+ const taskId = taskIdFromSession(session);
2347
+ if (!taskId)
2348
+ continue;
2349
+ const existing = byTask.get(taskId);
2350
+ if (!existing || (session.lastEventAt ?? "").localeCompare(existing.lastEventAt ?? "") >= 0) {
2351
+ byTask.set(taskId, session);
2352
+ }
2353
+ }
2354
+ return byTask;
2355
+ }
2356
+ function projectTaskStatusWithSessions(task, sessionsByTask = new Map) {
2357
+ const sessionStatus = projectRunStatusForTaskGrouping(sessionsByTask.get(task.id)?.status);
2358
+ return sessionStatus ?? projectTaskStatusForGrouping(task.status);
2359
+ }
2360
+ function groupTasksByProjectedStatus(tasks, sessions = []) {
2361
+ const sessionsByTask = latestSessionByTask(sessions);
2105
2362
  const byStatus = new Map;
2106
2363
  for (const task of tasks) {
2107
- const projectedStatus = projectTaskStatusForGrouping(task.status);
2364
+ const projectedStatus = projectTaskStatusWithSessions(task, sessionsByTask);
2108
2365
  const group = byStatus.get(projectedStatus);
2109
2366
  if (group) {
2110
2367
  group.push(task);
@@ -2128,6 +2385,69 @@ function selectTasksGroupedByStatus(snapshot, workspaceId) {
2128
2385
  }
2129
2386
  return result;
2130
2387
  }
2388
+ function isProjectionGroupingInput(value) {
2389
+ return Boolean(value && typeof value === "object" && !("snapshotSequence" in value) && Array.isArray(value.tasks));
2390
+ }
2391
+ function selectTasksGroupedByStatus(input, workspaceId) {
2392
+ if (isProjectionGroupingInput(input)) {
2393
+ const filteredTasks = input.workspaceId ? input.tasks.filter((task) => {
2394
+ const workspaceTask = task;
2395
+ return workspaceTask.workspaceId === input.workspaceId;
2396
+ }) : input.tasks;
2397
+ return groupTasksByProjectedStatus(filteredTasks, input.sessions);
2398
+ }
2399
+ const tasks = selectTasksByWorkspace(input, workspaceId ?? null);
2400
+ return groupTasksByProjectedStatus(tasks, []);
2401
+ }
2402
+ function isObjectRecord(value) {
2403
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2404
+ }
2405
+ function normalizeLogin(value) {
2406
+ return value.trim().replace(/^@+/, "").toLowerCase();
2407
+ }
2408
+ function assigneeLoginsFromValue(value) {
2409
+ if (!Array.isArray(value))
2410
+ return [];
2411
+ return value.flatMap((entry) => {
2412
+ if (typeof entry === "string" && entry.trim())
2413
+ return [normalizeLogin(entry)];
2414
+ if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
2415
+ return [normalizeLogin(entry.login)];
2416
+ }
2417
+ return [];
2418
+ });
2419
+ }
2420
+ function normalizeTaskAssigneeFilter(assignee, currentUserLogin) {
2421
+ const trimmed = assignee?.trim();
2422
+ if (!trimmed)
2423
+ return null;
2424
+ if (trimmed === "@me" || trimmed.toLowerCase() === "me") {
2425
+ return currentUserLogin?.trim() ? normalizeLogin(currentUserLogin) : null;
2426
+ }
2427
+ return normalizeLogin(trimmed);
2428
+ }
2429
+ function readTaskAssigneeLogins(task) {
2430
+ const taskRecord = task;
2431
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
2432
+ const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
2433
+ return Array.from(new Set([
2434
+ ...assigneeLoginsFromValue(taskRecord.assignees),
2435
+ ...assigneeLoginsFromValue(metadata?.assignees),
2436
+ ...assigneeLoginsFromValue(raw?.assignees)
2437
+ ]));
2438
+ }
2439
+ function taskMatchesAssigneeFilter(task, assignee, options = {}) {
2440
+ const normalized = normalizeTaskAssigneeFilter(assignee, options.currentUserLogin);
2441
+ if (!normalized)
2442
+ return false;
2443
+ return readTaskAssigneeLogins(task).includes(normalized);
2444
+ }
2445
+ function selectTasksAssignedTo(tasks, assignee, options = {}) {
2446
+ return tasks.filter((task) => taskMatchesAssigneeFilter(task, assignee, options));
2447
+ }
2448
+ function selectTasksAssignedToMe(tasks, currentUserLogin) {
2449
+ return selectTasksAssignedTo(tasks, "@me", currentUserLogin === undefined ? {} : { currentUserLogin });
2450
+ }
2131
2451
  function selectRun(snapshot, runId) {
2132
2452
  if (!runId)
2133
2453
  return null;
@@ -2188,18 +2508,120 @@ function selectUserInputsForWorkspace(snapshot, workspaceId) {
2188
2508
  const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
2189
2509
  return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
2190
2510
  }
2511
+ // packages/core/src/taskScore.ts
2512
+ var ROLE_WEIGHT = {
2513
+ architect: 25,
2514
+ extractor: 10
2515
+ };
2516
+ var CRITICALITY_WEIGHT = {
2517
+ core: 20,
2518
+ high: 10,
2519
+ normal: 0
2520
+ };
2521
+ function finiteNumber(value, fallback) {
2522
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
2523
+ }
2524
+ function scoreTask(input) {
2525
+ const priority = finiteNumber(input.priority, 3);
2526
+ const unblockCount = Math.max(0, finiteNumber(input.unblockCount, 0));
2527
+ const roleWeight = input.role ? ROLE_WEIGHT[input.role] ?? 0 : 0;
2528
+ const criticalityWeight = input.criticality ? CRITICALITY_WEIGHT[input.criticality] ?? 0 : 0;
2529
+ const validationWeight = (input.validation ?? []).some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
2530
+ const queueWeight = finiteNumber(input.queueWeight, 0);
2531
+ return unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + queueWeight;
2532
+ }
2533
+ function rankTasks(tasks, scoreInput, idOf) {
2534
+ return tasks.map((task) => {
2535
+ const input = scoreInput(task);
2536
+ return {
2537
+ task,
2538
+ score: scoreTask(input),
2539
+ priority: finiteNumber(input.priority, 3),
2540
+ unblockCount: Math.max(0, finiteNumber(input.unblockCount, 0))
2541
+ };
2542
+ }).toSorted((left, right) => {
2543
+ const scoreDelta = right.score - left.score;
2544
+ if (scoreDelta !== 0)
2545
+ return scoreDelta;
2546
+ const unblockDelta = right.unblockCount - left.unblockCount;
2547
+ if (unblockDelta !== 0)
2548
+ return unblockDelta;
2549
+ const priorityDelta = left.priority - right.priority;
2550
+ if (priorityDelta !== 0)
2551
+ return priorityDelta;
2552
+ return idOf(left.task).localeCompare(idOf(right.task));
2553
+ });
2554
+ }
2555
+
2191
2556
  // packages/core/src/taskGraph.ts
2192
- function isObjectRecord(value) {
2557
+ function isObjectRecord2(value) {
2193
2558
  return typeof value === "object" && value !== null && !Array.isArray(value);
2194
2559
  }
2195
- function readTaskMetadataStringList(task, key) {
2196
- const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
2197
- const value = metadata?.[key];
2560
+ function readStringList(value) {
2198
2561
  return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
2199
2562
  }
2563
+ function unique(values) {
2564
+ return Array.from(new Set(values));
2565
+ }
2566
+ function readTaskMetadataStringList(task, key) {
2567
+ const taskRecord = task;
2568
+ const topLevel = readStringList(taskRecord[key]);
2569
+ if (topLevel.length > 0)
2570
+ return topLevel;
2571
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2572
+ const metadataList = readStringList(metadata?.[key]);
2573
+ if (metadataList.length > 0)
2574
+ return metadataList;
2575
+ if (key === "dependencies") {
2576
+ return readStringList(metadata?.deps);
2577
+ }
2578
+ return [];
2579
+ }
2580
+ function readTaskBlockingDependencyRefs(task) {
2581
+ return readTaskMetadataStringList(task, "dependencies");
2582
+ }
2583
+ function readTaskDependencyRefs(task) {
2584
+ return unique([
2585
+ ...readTaskMetadataStringList(task, "dependencies"),
2586
+ ...readTaskMetadataStringList(task, "parentChildDeps")
2587
+ ]);
2588
+ }
2200
2589
  function readTaskSourceIssueId(task) {
2201
- const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
2202
- return typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0 ? metadata.sourceIssueId : null;
2590
+ if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
2591
+ return task.sourceIssueId;
2592
+ }
2593
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2594
+ if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
2595
+ return metadata.sourceIssueId;
2596
+ }
2597
+ const rigMetadata = isObjectRecord2(metadata?._rig) ? metadata._rig : null;
2598
+ return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
2599
+ }
2600
+ function readTaskScope(task) {
2601
+ const taskRecord = task;
2602
+ const topLevel = readStringList(taskRecord.scope);
2603
+ if (topLevel.length > 0)
2604
+ return unique(topLevel.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
2605
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2606
+ const metadataScope = readStringList(metadata?.scope);
2607
+ if (metadataScope.length > 0)
2608
+ return unique(metadataScope.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
2609
+ return unique([
2610
+ ...readStringList(metadata?.files),
2611
+ ...readStringList(metadata?.paths)
2612
+ ].map((entry) => entry.trim()).filter((entry) => entry.length > 0));
2613
+ }
2614
+ function isScopeList(input) {
2615
+ return Array.isArray(input);
2616
+ }
2617
+ function normalizeScopeInput(input) {
2618
+ return isScopeList(input) ? unique(input.map((entry) => entry.trim()).filter((entry) => entry.length > 0)) : readTaskScope(input);
2619
+ }
2620
+ function disjointScope(left, right) {
2621
+ const leftScope = new Set(normalizeScopeInput(left));
2622
+ if (leftScope.size === 0)
2623
+ return true;
2624
+ return normalizeScopeInput(right).every((entry) => !leftScope.has(entry));
2203
2625
  }
2204
2626
  function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
2205
2627
  if (tasksById.has(ref))
@@ -2229,11 +2651,7 @@ function computeTaskBlockingDepths(tasks) {
2229
2651
  if (!task)
2230
2652
  return 0;
2231
2653
  stack.add(taskId);
2232
- const refs = [
2233
- ...readTaskMetadataStringList(task, "dependencies"),
2234
- ...readTaskMetadataStringList(task, "parentChildDeps")
2235
- ];
2236
- const blockers = refs.map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
2654
+ const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
2237
2655
  const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
2238
2656
  stack.delete(taskId);
2239
2657
  memo.set(taskId, depth);
@@ -2244,6 +2662,212 @@ function computeTaskBlockingDepths(tasks) {
2244
2662
  }
2245
2663
  return memo;
2246
2664
  }
2665
+ function isTaskTerminalStatus(status) {
2666
+ switch (status) {
2667
+ case "closed":
2668
+ case "completed":
2669
+ case "done":
2670
+ case "cancelled":
2671
+ case "canceled":
2672
+ return true;
2673
+ default:
2674
+ return false;
2675
+ }
2676
+ }
2677
+ function isTaskBlockedStatus(status) {
2678
+ return status === "blocked";
2679
+ }
2680
+ function isTaskRunnableStatus(status) {
2681
+ if (status === null || status === undefined || status === "")
2682
+ return true;
2683
+ if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
2684
+ return false;
2685
+ switch (status) {
2686
+ case "ready":
2687
+ case "open":
2688
+ case "failed":
2689
+ return true;
2690
+ default:
2691
+ return false;
2692
+ }
2693
+ }
2694
+ function priorityValue(task) {
2695
+ return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
2696
+ }
2697
+ function readTaskRole(task) {
2698
+ if (typeof task.role === "string" && task.role.trim())
2699
+ return task.role.trim();
2700
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2701
+ return typeof metadata?.role === "string" && metadata.role.trim() ? metadata.role.trim() : null;
2702
+ }
2703
+ function readTaskCriticality(task) {
2704
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2705
+ return typeof metadata?.criticality === "string" && metadata.criticality.trim() ? metadata.criticality.trim() : null;
2706
+ }
2707
+ function readTaskValidationKeys(task) {
2708
+ const taskRecord = task;
2709
+ const topLevel = readStringList(taskRecord.validationKeys);
2710
+ if (topLevel.length > 0)
2711
+ return topLevel;
2712
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2713
+ return readStringList(metadata?.validation);
2714
+ }
2715
+ function readTaskQueueWeight(task) {
2716
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2717
+ const queueWeight = metadata?.queueWeight ?? metadata?.queue_weight;
2718
+ return typeof queueWeight === "number" && Number.isFinite(queueWeight) ? queueWeight : 0;
2719
+ }
2720
+ function scoreInputForTask(task, unblockCount) {
2721
+ return {
2722
+ priority: task.priority,
2723
+ unblockCount,
2724
+ role: readTaskRole(task),
2725
+ criticality: readTaskCriticality(task),
2726
+ validation: readTaskValidationKeys(task),
2727
+ queueWeight: readTaskQueueWeight(task)
2728
+ };
2729
+ }
2730
+ function computeTaskDependencyBadges(tasks) {
2731
+ const index = buildTaskReferenceIndex(tasks);
2732
+ const blockingDepths = computeTaskBlockingDepths(tasks);
2733
+ const dependencyIdsByTask = new Map;
2734
+ const unresolvedRefsByTask = new Map;
2735
+ const blocksByTask = new Map;
2736
+ for (const task of tasks) {
2737
+ const dependencyIds = [];
2738
+ const unresolvedRefs = [];
2739
+ for (const ref of readTaskBlockingDependencyRefs(task)) {
2740
+ const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
2741
+ if (dependencyId && dependencyId !== task.id) {
2742
+ dependencyIds.push(dependencyId);
2743
+ const blocks = blocksByTask.get(dependencyId);
2744
+ if (blocks) {
2745
+ blocks.push(task.id);
2746
+ } else {
2747
+ blocksByTask.set(dependencyId, [task.id]);
2748
+ }
2749
+ } else {
2750
+ unresolvedRefs.push(ref);
2751
+ }
2752
+ }
2753
+ dependencyIdsByTask.set(task.id, unique(dependencyIds));
2754
+ unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
2755
+ }
2756
+ const summaries = new Map;
2757
+ for (const task of tasks) {
2758
+ const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
2759
+ const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
2760
+ const blockedBy = dependencyIds.filter((dependencyId) => {
2761
+ const dependency = index.tasksById.get(dependencyId);
2762
+ return dependency ? !isTaskTerminalStatus(dependency.status) : false;
2763
+ });
2764
+ const blocks = unique(blocksByTask.get(task.id) ?? []);
2765
+ const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
2766
+ const ready = isTaskRunnableStatus(task.status) && !blocked;
2767
+ const badges = [];
2768
+ if (blocked) {
2769
+ badges.push({
2770
+ kind: "blocked",
2771
+ label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
2772
+ description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
2773
+ ...blockedBy.length > 0 ? { count: blockedBy.length } : {},
2774
+ taskIds: blockedBy
2775
+ });
2776
+ } else if (ready) {
2777
+ badges.push({
2778
+ kind: "ready",
2779
+ label: "ready",
2780
+ description: "No open dependencies block this task."
2781
+ });
2782
+ }
2783
+ if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
2784
+ badges.push({
2785
+ kind: "dependency",
2786
+ label: `deps ${dependencyIds.length}/${blocks.length}`,
2787
+ description: [
2788
+ dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
2789
+ blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
2790
+ unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
2791
+ ].filter((part) => part !== null).join(" "),
2792
+ count: dependencyIds.length + blocks.length,
2793
+ taskIds: unique([...dependencyIds, ...blocks])
2794
+ });
2795
+ }
2796
+ summaries.set(task.id, {
2797
+ taskId: task.id,
2798
+ blockingDepth: blockingDepths.get(task.id) ?? 0,
2799
+ dependencyIds,
2800
+ unresolvedDependencyRefs,
2801
+ blockedBy,
2802
+ blocks,
2803
+ blocked,
2804
+ ready,
2805
+ dependencyCount: dependencyIds.length,
2806
+ dependentCount: blocks.length,
2807
+ badges
2808
+ });
2809
+ }
2810
+ return summaries;
2811
+ }
2812
+ function selectNextReadyTaskByPriority(tasks, options = {}) {
2813
+ const excluded = new Set(options.excludeTaskIds ?? []);
2814
+ const badges = computeTaskDependencyBadges(tasks);
2815
+ const candidates = tasks.filter((task) => !excluded.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true).toSorted((left, right) => {
2816
+ const priorityDelta = priorityValue(left) - priorityValue(right);
2817
+ if (priorityDelta !== 0)
2818
+ return priorityDelta;
2819
+ const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
2820
+ if (createdDelta !== 0)
2821
+ return createdDelta;
2822
+ return left.id.localeCompare(right.id);
2823
+ });
2824
+ return candidates[0] ?? null;
2825
+ }
2826
+ function rankReadyTasks(tasks, options = {}) {
2827
+ const excluded = new Set(options.excludeTaskIds ?? []);
2828
+ const activeTaskIds = new Set(options.activeTaskIds ?? []);
2829
+ const badges = computeTaskDependencyBadges(tasks);
2830
+ const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
2831
+ const openBlockCount = (task) => (badges.get(task.id)?.blocks ?? []).filter((blockedTaskId) => {
2832
+ const blockedTask = tasksById.get(blockedTaskId);
2833
+ return blockedTask ? !isTaskTerminalStatus(blockedTask.status) : false;
2834
+ }).length;
2835
+ 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);
2836
+ const maxUnblockCount = Math.max(0, ...readyTasks.map(openBlockCount));
2837
+ const selectedByMode = readyTasks.filter((task) => {
2838
+ const unblockCount = openBlockCount(task);
2839
+ if (options.selection === "blocking-only")
2840
+ return unblockCount > 0;
2841
+ if (options.selection === "max-unblock")
2842
+ return maxUnblockCount > 0 && unblockCount === maxUnblockCount;
2843
+ return true;
2844
+ });
2845
+ return rankTasks(selectedByMode, (task) => scoreInputForTask(task, openBlockCount(task)), (task) => task.id).map((entry) => ({
2846
+ task: entry.task,
2847
+ score: entry.score,
2848
+ priority: entry.priority,
2849
+ unblockCount: entry.unblockCount,
2850
+ scope: readTaskScope(entry.task)
2851
+ }));
2852
+ }
2853
+ function selectRankedReadyTasks(tasks, options = {}) {
2854
+ const ranked = rankReadyTasks(tasks, options);
2855
+ if (options.requireDisjointScopes !== true && !options.disjointWithScopes) {
2856
+ return ranked.slice(0, options.limit).map((entry) => entry.task);
2857
+ }
2858
+ const occupiedScopes = new Set(options.disjointWithScopes ?? []);
2859
+ const selected = [];
2860
+ for (const entry of ranked) {
2861
+ if (entry.scope.some((scope) => occupiedScopes.has(scope)))
2862
+ continue;
2863
+ selected.push(entry.task);
2864
+ for (const scope of entry.scope)
2865
+ occupiedScopes.add(scope);
2866
+ if (options.limit !== undefined && selected.length >= options.limit)
2867
+ break;
2868
+ }
2869
+ return selected;
2870
+ }
2247
2871
  // packages/core/src/taskGraphCodes.ts
2248
2872
  var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
2249
2873
  function extractTaskCode(title) {
@@ -2283,12 +2907,15 @@ var PALETTE = [
2283
2907
  { bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
2284
2908
  { bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
2285
2909
  ];
2286
- function isObjectRecord2(value) {
2910
+ function isObjectRecord3(value) {
2287
2911
  return typeof value === "object" && value !== null && !Array.isArray(value);
2288
2912
  }
2289
2913
  function readIssueType(task) {
2290
- const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
2291
- return typeof metadata?.issueType === "string" ? metadata.issueType : null;
2914
+ const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
2915
+ if (typeof metadata?.issueType === "string")
2916
+ return metadata.issueType;
2917
+ const raw = isObjectRecord3(metadata?.raw) ? metadata.raw : null;
2918
+ return typeof raw?.issueType === "string" ? raw.issueType : null;
2292
2919
  }
2293
2920
  function isGraphTask(task) {
2294
2921
  return readIssueType(task) !== "epic";
@@ -2535,6 +3162,7 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
2535
3162
  const artifactCount = (snapshot?.artifacts ?? []).filter((artifact) => runIds.has(artifact.runId)).length;
2536
3163
  nodes.push({
2537
3164
  id: task.id,
3165
+ taskId: task.id,
2538
3166
  task,
2539
3167
  rowKey,
2540
3168
  rowLabel: rowLabelByKey.get(rowKey) ?? rowKey,
@@ -2569,6 +3197,631 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
2569
3197
  taskCount: graphTasks.length
2570
3198
  };
2571
3199
  }
3200
+ // packages/core/src/stageResolve.ts
3201
+ class PipelineUnresolvableError extends Error {
3202
+ cycles;
3203
+ contributors;
3204
+ constructor(message, cycles, contributors) {
3205
+ super(message);
3206
+ this.name = "PipelineUnresolvableError";
3207
+ this.cycles = cycles;
3208
+ this.contributors = contributors;
3209
+ }
3210
+ }
3211
+ function uniqueSorted(values) {
3212
+ return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
3213
+ }
3214
+ function wrapperForMutation(mutation) {
3215
+ return "wrapper" in mutation ? mutation.wrapper : mutation.around;
3216
+ }
3217
+ function contributorOf(mutation) {
3218
+ if (mutation.contributedBy?.trim())
3219
+ return mutation.contributedBy;
3220
+ if (mutation.op === "wrap") {
3221
+ const wrapper = wrapperForMutation(mutation);
3222
+ if (wrapper.id?.trim())
3223
+ return wrapper.id;
3224
+ }
3225
+ return "anonymous";
3226
+ }
3227
+ function stableMutationCompare(left, right) {
3228
+ const leftTarget = left.op === "insert" ? left.stage.id : left.id;
3229
+ const rightTarget = right.op === "insert" ? right.stage.id : right.id;
3230
+ const targetDelta = leftTarget.localeCompare(rightTarget);
3231
+ if (targetDelta !== 0)
3232
+ return targetDelta;
3233
+ return contributorOf(left).localeCompare(contributorOf(right));
3234
+ }
3235
+ function ensureUniqueStageIds(stages) {
3236
+ const seen = new Set;
3237
+ for (const stage of stages) {
3238
+ if (seen.has(stage.id)) {
3239
+ throw new PipelineUnresolvableError(`Duplicate stage id: ${stage.id}`, [], []);
3240
+ }
3241
+ seen.add(stage.id);
3242
+ }
3243
+ }
3244
+ function assertNoDuplicateMutationTargets(mutations, op) {
3245
+ const seen = new Map;
3246
+ for (const mutation of mutations.filter((entry) => entry.op === op)) {
3247
+ const id = mutation.op === "insert" ? mutation.stage.id : mutation.id;
3248
+ const previous = seen.get(id);
3249
+ if (previous) {
3250
+ throw new PipelineUnresolvableError(`Duplicate ${op} mutation for stage ${id}: ${previous}, ${contributorOf(mutation)}`, [], uniqueSorted([previous, contributorOf(mutation)]));
3251
+ }
3252
+ seen.set(id, contributorOf(mutation));
3253
+ }
3254
+ }
3255
+ function mergeAnchors(current, incoming) {
3256
+ return uniqueSorted([...current, ...incoming ?? []]);
3257
+ }
3258
+ function stagePriority(stage) {
3259
+ return typeof stage.priority === "number" && Number.isFinite(stage.priority) ? stage.priority : 0;
3260
+ }
3261
+ function readyCompare(states) {
3262
+ return (left, right) => {
3263
+ const leftState = states.get(left);
3264
+ const rightState = states.get(right);
3265
+ const priorityDelta = stagePriority(rightState?.stage ?? { id: right }) - stagePriority(leftState?.stage ?? { id: left });
3266
+ if (priorityDelta !== 0)
3267
+ return priorityDelta;
3268
+ if (leftState?.baseIndex !== null && rightState?.baseIndex !== null && leftState?.baseIndex !== rightState?.baseIndex) {
3269
+ return (leftState?.baseIndex ?? 0) - (rightState?.baseIndex ?? 0);
3270
+ }
3271
+ if (leftState?.baseIndex !== null && rightState?.baseIndex === null)
3272
+ return -1;
3273
+ if (leftState?.baseIndex === null && rightState?.baseIndex !== null)
3274
+ return 1;
3275
+ return left.localeCompare(right);
3276
+ };
3277
+ }
3278
+ function findCycles(nodes, edges) {
3279
+ const adjacency = new Map;
3280
+ for (const node of nodes)
3281
+ adjacency.set(node, []);
3282
+ for (const edge of edges)
3283
+ adjacency.get(edge.from)?.push(edge.to);
3284
+ for (const targets of adjacency.values())
3285
+ targets.sort((left, right) => left.localeCompare(right));
3286
+ let nextIndex = 0;
3287
+ const indexByNode = new Map;
3288
+ const lowByNode = new Map;
3289
+ const stack = [];
3290
+ const onStack = new Set;
3291
+ const cycles = [];
3292
+ const visit = (node) => {
3293
+ indexByNode.set(node, nextIndex);
3294
+ lowByNode.set(node, nextIndex);
3295
+ nextIndex += 1;
3296
+ stack.push(node);
3297
+ onStack.add(node);
3298
+ for (const target of adjacency.get(node) ?? []) {
3299
+ if (!indexByNode.has(target)) {
3300
+ visit(target);
3301
+ lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, lowByNode.get(target) ?? 0));
3302
+ } else if (onStack.has(target)) {
3303
+ lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, indexByNode.get(target) ?? 0));
3304
+ }
3305
+ }
3306
+ if (lowByNode.get(node) !== indexByNode.get(node))
3307
+ return;
3308
+ const component = [];
3309
+ let current;
3310
+ do {
3311
+ current = stack.pop();
3312
+ if (current) {
3313
+ onStack.delete(current);
3314
+ component.push(current);
3315
+ }
3316
+ } while (current && current !== node);
3317
+ const hasSelfLoop = edges.some((edge) => edge.from === node && edge.to === node);
3318
+ if (component.length > 1 || hasSelfLoop) {
3319
+ cycles.push(component.sort((left, right) => left.localeCompare(right)));
3320
+ }
3321
+ };
3322
+ for (const node of [...nodes].sort((left, right) => left.localeCompare(right))) {
3323
+ if (!indexByNode.has(node))
3324
+ visit(node);
3325
+ }
3326
+ return cycles.sort((left, right) => left.join("\x00").localeCompare(right.join("\x00")));
3327
+ }
3328
+ function topologicalOrder(states, edges) {
3329
+ const nodes = [...states.keys()];
3330
+ const outgoing = new Map;
3331
+ const indegree = new Map;
3332
+ for (const node of nodes) {
3333
+ outgoing.set(node, []);
3334
+ indegree.set(node, 0);
3335
+ }
3336
+ for (const edge of edges) {
3337
+ outgoing.get(edge.from)?.push(edge.to);
3338
+ indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1);
3339
+ }
3340
+ for (const targets of outgoing.values())
3341
+ targets.sort((left, right) => left.localeCompare(right));
3342
+ const compare = readyCompare(states);
3343
+ const ready = nodes.filter((node) => (indegree.get(node) ?? 0) === 0).sort(compare);
3344
+ const order = [];
3345
+ while (ready.length > 0) {
3346
+ const node = ready.shift();
3347
+ if (!node)
3348
+ break;
3349
+ order.push(node);
3350
+ for (const target of outgoing.get(node) ?? []) {
3351
+ const next = (indegree.get(target) ?? 0) - 1;
3352
+ indegree.set(target, next);
3353
+ if (next === 0) {
3354
+ ready.push(target);
3355
+ ready.sort(compare);
3356
+ }
3357
+ }
3358
+ }
3359
+ if (order.length === nodes.length)
3360
+ return order;
3361
+ const cycles = findCycles(nodes, edges);
3362
+ const cycleMembers = new Set(cycles.flat());
3363
+ const contributors = uniqueSorted(edges.filter((edge) => cycleMembers.has(edge.from) && cycleMembers.has(edge.to)).flatMap((edge) => edge.contributors));
3364
+ throw new PipelineUnresolvableError(`Stage pipeline has unresolved cycle: ${cycles.map((cycle) => cycle.join(" -> ")).join("; ")}`, cycles, contributors);
3365
+ }
3366
+ function composeStageWrappers(state) {
3367
+ const wrappers = state.wrappers.toSorted((left, right) => {
3368
+ const priorityDelta = (right.wrapper.priority ?? 0) - (left.wrapper.priority ?? 0);
3369
+ if (priorityDelta !== 0)
3370
+ return priorityDelta;
3371
+ return left.contributedBy.localeCompare(right.contributedBy);
3372
+ });
3373
+ let stage = state.stage;
3374
+ for (const wrapper of wrappers.toReversed()) {
3375
+ if (wrapper.wrapper.apply)
3376
+ stage = wrapper.wrapper.apply(stage);
3377
+ }
3378
+ return stage;
3379
+ }
3380
+ function resolveStagePipeline(input) {
3381
+ ensureUniqueStageIds(input.defaultStages);
3382
+ const mutations = [...input.mutations ?? []];
3383
+ assertNoDuplicateMutationTargets(mutations, "insert");
3384
+ assertNoDuplicateMutationTargets(mutations, "replace");
3385
+ const states = new Map;
3386
+ const removedStates = new Map;
3387
+ for (const [index, stage] of input.defaultStages.entries()) {
3388
+ states.set(stage.id, {
3389
+ stage: { ...stage, protected: false },
3390
+ before: [...stage.before ?? []],
3391
+ after: [...stage.after ?? []],
3392
+ baseIndex: index,
3393
+ contributedBy: "default",
3394
+ wrappers: [],
3395
+ droppedAnchors: [],
3396
+ isProtected: false
3397
+ });
3398
+ }
3399
+ for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
3400
+ const state = states.get(mutation.id);
3401
+ const contributor = contributorOf(mutation);
3402
+ if (!state)
3403
+ continue;
3404
+ const removedState = {
3405
+ ...state,
3406
+ removedBy: contributor
3407
+ };
3408
+ removedStates.set(mutation.id, removedState);
3409
+ states.delete(mutation.id);
3410
+ }
3411
+ for (const mutation of mutations.filter((entry) => entry.op === "replace").sort(stableMutationCompare)) {
3412
+ const state = states.get(mutation.id);
3413
+ const contributor = contributorOf(mutation);
3414
+ if (!state)
3415
+ continue;
3416
+ const replacement = { ...mutation.stage, id: mutation.id, protected: false };
3417
+ states.set(mutation.id, {
3418
+ ...state,
3419
+ stage: replacement,
3420
+ before: mutation.stage.before ? [...mutation.stage.before] : state.before,
3421
+ after: mutation.stage.after ? [...mutation.stage.after] : state.after,
3422
+ replacedBy: contributor,
3423
+ contributedBy: state.contributedBy,
3424
+ isProtected: false
3425
+ });
3426
+ }
3427
+ for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
3428
+ const contributor = contributorOf(mutation);
3429
+ if (states.has(mutation.stage.id)) {
3430
+ throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
3431
+ }
3432
+ states.set(mutation.stage.id, {
3433
+ stage: { ...mutation.stage, protected: false },
3434
+ before: [...mutation.stage.before ?? []],
3435
+ after: [...mutation.stage.after ?? []],
3436
+ baseIndex: null,
3437
+ contributedBy: contributor,
3438
+ wrappers: [],
3439
+ droppedAnchors: [],
3440
+ isProtected: false
3441
+ });
3442
+ }
3443
+ for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
3444
+ const state = states.get(mutation.id);
3445
+ if (!state)
3446
+ continue;
3447
+ states.set(mutation.id, {
3448
+ ...state,
3449
+ before: mergeAnchors(state.before, mutation.before),
3450
+ after: mergeAnchors(state.after, mutation.after)
3451
+ });
3452
+ }
3453
+ for (const mutation of mutations.filter((entry) => entry.op === "wrap").sort(stableMutationCompare)) {
3454
+ const state = states.get(mutation.id);
3455
+ const contributor = contributorOf(mutation);
3456
+ const wrapper = wrapperForMutation(mutation);
3457
+ if (!state)
3458
+ continue;
3459
+ states.set(mutation.id, {
3460
+ ...state,
3461
+ wrappers: [...state.wrappers, { contributedBy: contributor, wrapper }]
3462
+ });
3463
+ }
3464
+ const contributorsForAnchor = (stageId, direction, anchor, state) => {
3465
+ const contributors = new Set;
3466
+ if (state.replacedBy)
3467
+ contributors.add(state.replacedBy);
3468
+ for (const mutation of mutations) {
3469
+ if (mutation.op === "reorder" && mutation.id === stageId && (direction === "before" ? mutation.before : mutation.after)?.includes(anchor)) {
3470
+ contributors.add(contributorOf(mutation));
3471
+ }
3472
+ if (mutation.op === "insert" && mutation.stage.id === stageId && (direction === "before" ? mutation.stage.before : mutation.stage.after)?.includes(anchor)) {
3473
+ contributors.add(contributorOf(mutation));
3474
+ }
3475
+ }
3476
+ if (contributors.size === 0)
3477
+ contributors.add(state.contributedBy);
3478
+ return uniqueSorted(contributors);
3479
+ };
3480
+ const edges = [];
3481
+ for (const [stageId, state] of states) {
3482
+ const before = [];
3483
+ const after = [];
3484
+ for (const anchor of state.before) {
3485
+ if (states.has(anchor))
3486
+ before.push(anchor);
3487
+ else {
3488
+ const removed = removedStates.get(anchor);
3489
+ state.droppedAnchors.push({
3490
+ stageId,
3491
+ anchor,
3492
+ direction: "before",
3493
+ reason: removed ? "removed" : "missing",
3494
+ ...removed?.removedBy ? { removedBy: removed.removedBy } : {}
3495
+ });
3496
+ }
3497
+ }
3498
+ for (const anchor of state.after) {
3499
+ if (states.has(anchor))
3500
+ after.push(anchor);
3501
+ else {
3502
+ const removed = removedStates.get(anchor);
3503
+ state.droppedAnchors.push({
3504
+ stageId,
3505
+ anchor,
3506
+ direction: "after",
3507
+ reason: removed ? "removed" : "missing",
3508
+ ...removed?.removedBy ? { removedBy: removed.removedBy } : {}
3509
+ });
3510
+ }
3511
+ }
3512
+ state.before = uniqueSorted(before);
3513
+ state.after = uniqueSorted(after);
3514
+ for (const target of state.before)
3515
+ edges.push({ from: stageId, to: target, contributors: contributorsForAnchor(stageId, "before", target, state) });
3516
+ for (const source of state.after)
3517
+ edges.push({ from: source, to: stageId, contributors: contributorsForAnchor(stageId, "after", source, state) });
3518
+ }
3519
+ const order = topologicalOrder(states, edges);
3520
+ const stages = [];
3521
+ const record = [];
3522
+ for (const id of order) {
3523
+ const state = states.get(id);
3524
+ if (!state)
3525
+ continue;
3526
+ stages.push(composeStageWrappers(state));
3527
+ const wrappedBy = state.wrappers.toSorted((left, right) => {
3528
+ const priorityDelta = (right.wrapper.priority ?? 0) - (left.wrapper.priority ?? 0);
3529
+ if (priorityDelta !== 0)
3530
+ return priorityDelta;
3531
+ return left.contributedBy.localeCompare(right.contributedBy);
3532
+ }).map((wrapper) => wrapper.contributedBy);
3533
+ record.push({
3534
+ stageId: id,
3535
+ contributedBy: state.contributedBy,
3536
+ ...state.replacedBy ? { replacedBy: state.replacedBy } : {},
3537
+ ...wrappedBy.length > 0 ? { wrappedBy } : {},
3538
+ ...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
3539
+ isProtected: state.isProtected
3540
+ });
3541
+ }
3542
+ record.push(...[...removedStates.entries()].toSorted((left, right) => {
3543
+ const leftIndex = left[1].baseIndex ?? Number.MAX_SAFE_INTEGER;
3544
+ const rightIndex = right[1].baseIndex ?? Number.MAX_SAFE_INTEGER;
3545
+ if (leftIndex !== rightIndex)
3546
+ return leftIndex - rightIndex;
3547
+ return left[0].localeCompare(right[0]);
3548
+ }).map(([stageId, state]) => ({
3549
+ stageId,
3550
+ contributedBy: state.contributedBy,
3551
+ ...state.removedBy ? { removedBy: state.removedBy } : {},
3552
+ isProtected: state.isProtected
3553
+ })));
3554
+ return { stages, order, record, cycles: [] };
3555
+ }
3556
+ // packages/core/src/dependencyGraph.ts
3557
+ function uniqueSorted2(values) {
3558
+ return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
3559
+ }
3560
+ function deriveGraphId(tasks) {
3561
+ const basis = tasks.map((task) => String(task.id)).toSorted((left, right) => left.localeCompare(right)).join("|");
3562
+ let hash = 2166136261;
3563
+ for (let index = 0;index < basis.length; index += 1) {
3564
+ hash ^= basis.charCodeAt(index);
3565
+ hash = Math.imul(hash, 16777619);
3566
+ }
3567
+ return `graph-${(hash >>> 0).toString(16).padStart(8, "0")}`;
3568
+ }
3569
+ function dedupeEdges(edges) {
3570
+ const byKey = new Map;
3571
+ for (const edge of edges) {
3572
+ byKey.set(`${edge.type}\x00${edge.fromTaskId}\x00${edge.toTaskId}`, edge);
3573
+ }
3574
+ return [...byKey.values()].toSorted((left, right) => {
3575
+ const typeDelta = left.type.localeCompare(right.type);
3576
+ if (typeDelta !== 0)
3577
+ return typeDelta;
3578
+ const fromDelta = left.fromTaskId.localeCompare(right.fromTaskId);
3579
+ if (fromDelta !== 0)
3580
+ return fromDelta;
3581
+ return left.toTaskId.localeCompare(right.toTaskId);
3582
+ });
3583
+ }
3584
+ function detectBlockingCycles(tasks, edges) {
3585
+ const taskIds = tasks.map((task) => String(task.id)).toSorted((left, right) => left.localeCompare(right));
3586
+ const adjacency = new Map;
3587
+ for (const taskId of taskIds)
3588
+ adjacency.set(taskId, []);
3589
+ for (const edge of edges) {
3590
+ if (edge.type === "blocks")
3591
+ adjacency.get(edge.fromTaskId)?.push(edge.toTaskId);
3592
+ }
3593
+ for (const targets of adjacency.values())
3594
+ targets.sort((left, right) => left.localeCompare(right));
3595
+ let nextIndex = 0;
3596
+ const indexByNode = new Map;
3597
+ const lowByNode = new Map;
3598
+ const stack = [];
3599
+ const onStack = new Set;
3600
+ const cycles = [];
3601
+ const visit = (node) => {
3602
+ indexByNode.set(node, nextIndex);
3603
+ lowByNode.set(node, nextIndex);
3604
+ nextIndex += 1;
3605
+ stack.push(node);
3606
+ onStack.add(node);
3607
+ for (const target of adjacency.get(node) ?? []) {
3608
+ if (!indexByNode.has(target)) {
3609
+ visit(target);
3610
+ lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, lowByNode.get(target) ?? 0));
3611
+ } else if (onStack.has(target)) {
3612
+ lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, indexByNode.get(target) ?? 0));
3613
+ }
3614
+ }
3615
+ if (lowByNode.get(node) !== indexByNode.get(node))
3616
+ return;
3617
+ const component = [];
3618
+ let current;
3619
+ do {
3620
+ current = stack.pop();
3621
+ if (current) {
3622
+ onStack.delete(current);
3623
+ component.push(current);
3624
+ }
3625
+ } while (current && current !== node);
3626
+ const selfLoop = (adjacency.get(node) ?? []).includes(node);
3627
+ if (component.length > 1 || selfLoop)
3628
+ cycles.push(component.sort((left, right) => left.localeCompare(right)));
3629
+ };
3630
+ for (const taskId of taskIds) {
3631
+ if (!indexByNode.has(taskId))
3632
+ visit(taskId);
3633
+ }
3634
+ return cycles.sort((left, right) => left.join("\x00").localeCompare(right.join("\x00")));
3635
+ }
3636
+ function buildDependencyGraphModel(tasks, options = {}) {
3637
+ const badges = computeTaskDependencyBadges(tasks);
3638
+ const index = buildTaskReferenceIndex(tasks);
3639
+ const edges = [];
3640
+ const unresolvedRefs = [];
3641
+ for (const task of tasks) {
3642
+ for (const ref of readTaskMetadataStringList(task, "dependencies")) {
3643
+ const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
3644
+ if (dependencyId)
3645
+ edges.push({ fromTaskId: dependencyId, toTaskId: String(task.id), type: "blocks" });
3646
+ else
3647
+ unresolvedRefs.push(ref);
3648
+ }
3649
+ for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
3650
+ const parentId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
3651
+ if (parentId)
3652
+ edges.push({ fromTaskId: parentId, toTaskId: String(task.id), type: "parent-child" });
3653
+ else
3654
+ unresolvedRefs.push(ref);
3655
+ }
3656
+ }
3657
+ const dedupedEdges = dedupeEdges(edges);
3658
+ const nodes = tasks.map((task) => {
3659
+ const summary = badges.get(task.id);
3660
+ const assignees = readTaskAssigneeLogins(task);
3661
+ const groupKey = extractTaskGroupKey(task.title);
3662
+ return {
3663
+ taskId: String(task.id),
3664
+ title: task.title,
3665
+ status: task.status,
3666
+ priority: task.priority,
3667
+ assignee: assignees[0] ?? null,
3668
+ blockedBy: summary?.blockedBy ?? [],
3669
+ blocks: summary?.blocks ?? [],
3670
+ blockingDepth: summary?.blockingDepth ?? 0,
3671
+ blockerClass: null,
3672
+ actionRiskTier: null,
3673
+ epicKey: groupKey,
3674
+ groupKey,
3675
+ externalId: task.externalId,
3676
+ sourceIssueId: task.sourceIssueId ?? null,
3677
+ scope: task.scope,
3678
+ validationKeys: task.validationKeys
3679
+ };
3680
+ }).toSorted((left, right) => left.taskId.localeCompare(right.taskId));
3681
+ return {
3682
+ graphId: options.graphId ?? deriveGraphId(tasks),
3683
+ nodes,
3684
+ edges: dedupedEdges,
3685
+ layout: buildTaskGraphLayout(options.snapshot ?? null, tasks, { showParentChild: options.showParentChild ?? true }),
3686
+ cycles: detectBlockingCycles(tasks, dedupedEdges),
3687
+ unresolvedRefs: uniqueSorted2(unresolvedRefs),
3688
+ degraded: false,
3689
+ generatedAt: options.generatedAt ?? new Date().toISOString()
3690
+ };
3691
+ }
3692
+ // packages/core/src/rollups.ts
3693
+ import { isOperatorActiveRunStatus } from "@rig/contracts";
3694
+ var UNASSIGNED_EPIC = "(unassigned-epic)";
3695
+ var UNASSIGNED_ASSIGNEE = "(unassigned)";
3696
+ var HUMAN_BLOCKER_CLASSES = {
3697
+ "human-decision": true,
3698
+ "human-approval": true,
3699
+ "external-input": true,
3700
+ unknown: true
3701
+ };
3702
+ function isObjectRecord4(value) {
3703
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3704
+ }
3705
+ function readIssueType2(task) {
3706
+ const metadata = isObjectRecord4(task.metadata) ? task.metadata : null;
3707
+ const raw = isObjectRecord4(metadata?.raw) ? metadata.raw : null;
3708
+ const value = raw?.issueType ?? metadata?.issueType;
3709
+ return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : null;
3710
+ }
3711
+ function isEpicTask(task) {
3712
+ return readIssueType2(task) === "epic";
3713
+ }
3714
+ function isInFlightTaskStatus(status) {
3715
+ switch (status) {
3716
+ case "queued":
3717
+ case "running":
3718
+ case "in_progress":
3719
+ case "under_review":
3720
+ return true;
3721
+ default:
3722
+ return false;
3723
+ }
3724
+ }
3725
+ function epicKeyForTask(task, tasksById, resolve) {
3726
+ const parentRef = readTaskMetadataStringList(task, "parentChildDeps")[0];
3727
+ const parentId = parentRef ? resolve(parentRef) : null;
3728
+ const parent = parentId ? tasksById.get(parentId) : null;
3729
+ if (parent)
3730
+ return extractTaskGroupKey(parent.title) ?? parent.title ?? parent.id;
3731
+ return extractTaskGroupKey(task.title) ?? UNASSIGNED_EPIC;
3732
+ }
3733
+ function rollupByEpic(tasks, classifications = new Map) {
3734
+ const index = buildTaskReferenceIndex(tasks);
3735
+ const badges = computeTaskDependencyBadges(tasks);
3736
+ const resolve = (ref) => resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
3737
+ const buckets = new Map;
3738
+ for (const task of tasks) {
3739
+ if (isEpicTask(task))
3740
+ continue;
3741
+ const epicKey = epicKeyForTask(task, index.tasksById, resolve);
3742
+ const current = buckets.get(epicKey) ?? {
3743
+ total: 0,
3744
+ completed: 0,
3745
+ blockedCount: 0,
3746
+ humanBlockedCount: 0,
3747
+ inFlightCount: 0,
3748
+ byStatus: {}
3749
+ };
3750
+ current.total += 1;
3751
+ if (isTaskTerminalStatus(task.status))
3752
+ current.completed += 1;
3753
+ if (badges.get(task.id)?.blocked === true)
3754
+ current.blockedCount += 1;
3755
+ if (isInFlightTaskStatus(task.status))
3756
+ current.inFlightCount += 1;
3757
+ const classification = classifications.get(task.id);
3758
+ if (classification && HUMAN_BLOCKER_CLASSES[classification])
3759
+ current.humanBlockedCount += 1;
3760
+ current.byStatus[task.status] = (current.byStatus[task.status] ?? 0) + 1;
3761
+ buckets.set(epicKey, current);
3762
+ }
3763
+ return [...buckets.entries()].map(([epicKey, bucket]) => ({
3764
+ epicKey,
3765
+ total: bucket.total,
3766
+ percentComplete: bucket.total === 0 ? 0 : Math.round(100 * bucket.completed / bucket.total),
3767
+ blockedCount: bucket.blockedCount,
3768
+ humanBlockedCount: bucket.humanBlockedCount,
3769
+ inFlightCount: bucket.inFlightCount,
3770
+ byStatus: Object.fromEntries(Object.entries(bucket.byStatus).sort(([left], [right]) => left.localeCompare(right)))
3771
+ })).toSorted((left, right) => left.epicKey.localeCompare(right.epicKey));
3772
+ }
3773
+ function assigneesForTask(task) {
3774
+ const assignees = readTaskAssigneeLogins(task);
3775
+ return assignees.length > 0 ? assignees : [UNASSIGNED_ASSIGNEE];
3776
+ }
3777
+ function rollupByAssignee(tasks, runs) {
3778
+ const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
3779
+ const badges = computeTaskDependencyBadges(tasks);
3780
+ const buckets = new Map;
3781
+ const ensureBucket = (assignee) => {
3782
+ const existing = buckets.get(assignee);
3783
+ if (existing)
3784
+ return existing;
3785
+ const created = { openTaskCount: 0, inFlightRunIds: new Set, prsAwaitingReview: 0, blockers: new Set };
3786
+ buckets.set(assignee, created);
3787
+ return created;
3788
+ };
3789
+ for (const task of tasks) {
3790
+ if (isEpicTask(task))
3791
+ continue;
3792
+ for (const assignee of assigneesForTask(task)) {
3793
+ const bucket = ensureBucket(assignee);
3794
+ if (!isTaskTerminalStatus(task.status))
3795
+ bucket.openTaskCount += 1;
3796
+ if (badges.get(task.id)?.blocked === true)
3797
+ bucket.blockers.add(task.id);
3798
+ if (task.status === "under_review")
3799
+ bucket.prsAwaitingReview += 1;
3800
+ }
3801
+ }
3802
+ for (const run of runs) {
3803
+ const taskId = run.record.taskId;
3804
+ const task = taskId ? tasksById.get(taskId) : null;
3805
+ if (!task)
3806
+ continue;
3807
+ if (isEpicTask(task))
3808
+ continue;
3809
+ for (const assignee of assigneesForTask(task)) {
3810
+ const bucket = ensureBucket(assignee);
3811
+ if (run.status && isOperatorActiveRunStatus(run.status))
3812
+ bucket.inFlightRunIds.add(run.record.runId ?? `${String(task.id)}:${run.lastSeq}`);
3813
+ if (run.status === "reviewing" && run.record.prUrl)
3814
+ bucket.prsAwaitingReview += 1;
3815
+ }
3816
+ }
3817
+ return [...buckets.entries()].map(([assignee, bucket]) => ({
3818
+ assignee,
3819
+ openTaskCount: bucket.openTaskCount,
3820
+ inFlightRunCount: bucket.inFlightRunIds.size,
3821
+ prsAwaitingReview: bucket.prsAwaitingReview,
3822
+ blockers: [...bucket.blockers].toSorted((left, right) => String(left).localeCompare(String(right)))
3823
+ })).toSorted((left, right) => left.assignee.localeCompare(right.assignee));
3824
+ }
2572
3825
 
2573
3826
  // packages/core/src/index.ts
2574
3827
  var RIG_CORE_PACKAGE = "@rig/core";
@@ -2582,35 +3835,58 @@ export {
2582
3835
  selectTasksForWorkspace,
2583
3836
  selectTasksByWorkspace,
2584
3837
  selectTasksByStatus,
3838
+ selectTasksAssignedToMe,
3839
+ selectTasksAssignedTo,
2585
3840
  selectTask,
2586
3841
  selectRunsForWorkspace,
2587
3842
  selectRunsForTask,
2588
3843
  selectRunsByTask,
2589
3844
  selectRun,
3845
+ selectRankedReadyTasks,
2590
3846
  selectQueueForWorkspace,
2591
3847
  selectPrimaryWorkspace,
2592
3848
  selectPendingApprovals,
3849
+ selectNextReadyTaskByPriority,
2593
3850
  selectGraphsForWorkspace,
2594
3851
  selectApprovalsForWorkspace,
2595
3852
  selectApprovalsForRun,
2596
3853
  selectAdhocRunsForWorkspace,
2597
3854
  selectAdhocRuns,
3855
+ scoreTask,
3856
+ rollupByEpic,
3857
+ rollupByAssignee,
2598
3858
  resolveTaskReference,
3859
+ resolveStagePipeline,
2599
3860
  readTaskSourceIssueId,
3861
+ readTaskScope,
2600
3862
  readTaskMetadataStringList,
3863
+ readTaskDependencyRefs,
3864
+ readTaskBlockingDependencyRefs,
3865
+ readTaskAssigneeLogins,
3866
+ rankTasks,
3867
+ rankReadyTasks,
2601
3868
  pruneQueueEntries,
3869
+ projectTaskStatusWithSessions,
3870
+ projectTaskStatusForGrouping,
3871
+ projectRunStatusForTaskGrouping,
2602
3872
  pickDefaultWorkspaceId,
3873
+ normalizeTaskAssigneeFilter,
3874
+ isTaskTerminalStatus,
2603
3875
  extractTaskGroupKey,
2604
3876
  extractTaskCode,
3877
+ disjointScope,
2605
3878
  definePlugin,
2606
3879
  defineConfig,
2607
3880
  createPluginHost,
3881
+ computeTaskDependencyBadges,
2608
3882
  computeTaskBlockingDepths,
2609
3883
  buildTaskReferenceIndex,
2610
3884
  buildTaskGraphLayout,
2611
3885
  buildRigInitConfigSource,
3886
+ buildDependencyGraphModel,
2612
3887
  applyEngineEvent as applyRigEvent,
2613
3888
  applyEngineEvents,
2614
3889
  applyEngineEvent,
2615
- RIG_CORE_PACKAGE
3890
+ RIG_CORE_PACKAGE,
3891
+ PipelineUnresolvableError
2616
3892
  };