@h-rig/core 0.0.6-alpha.9 → 0.0.6-alpha.91
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/define-config.d.ts +18 -0
- package/dist/src/define-config.js +20 -2
- package/dist/src/define-plugin.d.ts +9 -0
- package/dist/src/define-plugin.js +17 -0
- package/dist/src/engineReadModelReducer.d.ts +12 -0
- package/dist/src/engineReadModelReducer.js +6 -3
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +352 -21
- package/dist/src/load-config.d.ts +2 -0
- package/dist/src/load-config.js +257 -5
- package/dist/src/plugin-host.d.ts +40 -0
- package/dist/src/plugin-host.js +18 -0
- package/dist/src/plugin-runtime.d.ts +64 -0
- package/dist/src/rig-init-builder.d.ts +24 -0
- package/dist/src/rig-init-builder.js +1 -1
- package/dist/src/rigSelectors.d.ts +220 -0
- package/dist/src/rigSelectors.js +125 -4
- package/dist/src/taskGraph.d.ts +41 -0
- package/dist/src/taskGraph.js +164 -8
- package/dist/src/taskGraphCodes.d.ts +3 -0
- package/dist/src/taskGraphLayout.d.ts +60 -0
- package/dist/src/taskGraphLayout.js +22 -3
- package/package.json +6 -12
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RigConfig } from "@rig/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* Author-facing config input: `plugins` and `workspace` are optional and
|
|
4
|
+
* default to a plugin-less worktree setup, so the minimal documented config
|
|
5
|
+
* (project + taskSource) works out of the box.
|
|
6
|
+
*/
|
|
7
|
+
export type RigConfigInput = Omit<RigConfig, "plugins" | "workspace"> & {
|
|
8
|
+
plugins?: RigConfig["plugins"];
|
|
9
|
+
workspace?: RigConfig["workspace"];
|
|
10
|
+
};
|
|
11
|
+
/** Fill optional top-level sections before Schema decode (which requires them). */
|
|
12
|
+
export declare function applyConfigDefaults(raw: unknown): unknown;
|
|
13
|
+
/**
|
|
14
|
+
* Validate a Rig config and decode through Schema, preserving each plugin's
|
|
15
|
+
* `__runtime` channel. Schema decode strips unknown fields, so we capture
|
|
16
|
+
* the runtime objects before decoding and re-attach them after.
|
|
17
|
+
*/
|
|
18
|
+
export declare function defineConfig(cfg: RigConfigInput): RigConfig;
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
// packages/core/src/define-config.ts
|
|
3
3
|
import { Schema } from "effect";
|
|
4
4
|
import { RigConfig } from "@rig/contracts";
|
|
5
|
+
function normalizeWorkspaceConfig(raw) {
|
|
6
|
+
const workspace = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : { mainRepo: "." };
|
|
7
|
+
workspace.checkout = workspace.checkout ?? workspace.isolation ?? "worktree";
|
|
8
|
+
workspace.isolation = workspace.isolation ?? workspace.checkout;
|
|
9
|
+
workspace.sandbox = workspace.sandbox ?? "enforce";
|
|
10
|
+
return workspace;
|
|
11
|
+
}
|
|
12
|
+
function applyConfigDefaults(raw) {
|
|
13
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
14
|
+
return raw;
|
|
15
|
+
const record = raw;
|
|
16
|
+
return {
|
|
17
|
+
...record,
|
|
18
|
+
plugins: Array.isArray(record.plugins) ? record.plugins : [],
|
|
19
|
+
workspace: normalizeWorkspaceConfig(record.workspace)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
5
22
|
function defineConfig(cfg) {
|
|
6
23
|
const runtimeByName = new Map;
|
|
7
24
|
const plugins = cfg.plugins ?? [];
|
|
@@ -10,7 +27,7 @@ function defineConfig(cfg) {
|
|
|
10
27
|
runtimeByName.set(plugin.name, plugin.__runtime);
|
|
11
28
|
}
|
|
12
29
|
}
|
|
13
|
-
const decoded = Schema.decodeUnknownSync(RigConfig)(cfg);
|
|
30
|
+
const decoded = Schema.decodeUnknownSync(RigConfig)(applyConfigDefaults(cfg));
|
|
14
31
|
const decodedPlugins = decoded.plugins.map((p) => {
|
|
15
32
|
const runtime = runtimeByName.get(p.name);
|
|
16
33
|
if (!runtime)
|
|
@@ -20,5 +37,6 @@ function defineConfig(cfg) {
|
|
|
20
37
|
return { ...decoded, plugins: decodedPlugins };
|
|
21
38
|
}
|
|
22
39
|
export {
|
|
23
|
-
defineConfig
|
|
40
|
+
defineConfig,
|
|
41
|
+
applyConfigDefaults
|
|
24
42
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RigPlugin } from "@rig/contracts";
|
|
2
|
+
import type { RigPluginRuntime, RigPluginWithRuntime } from "./plugin-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Define a Rig plugin. The first argument is the schema-validated metadata
|
|
5
|
+
* (id strings, version, contributions). The optional second argument carries
|
|
6
|
+
* executable bits (validator `run` functions, etc.) that Schema cannot
|
|
7
|
+
* represent — they travel alongside the validated metadata as `__runtime`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function definePlugin(meta: RigPlugin, runtime?: RigPluginRuntime): RigPluginWithRuntime;
|
|
@@ -43,6 +43,23 @@ 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
|
+
}
|
|
46
63
|
return { ...validated, __runtime: runtime };
|
|
47
64
|
}
|
|
48
65
|
export {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EngineEvent, EngineReadModel, QueueEntry, RunSummary, TaskSummary } from "@rig/contracts";
|
|
2
|
+
type EngineApplyStatus = "applied" | "ignored" | "requires-resync";
|
|
3
|
+
export type ApplyStatus = EngineApplyStatus;
|
|
4
|
+
export interface EngineEventApplyResult {
|
|
5
|
+
readonly status: EngineApplyStatus;
|
|
6
|
+
readonly snapshot: EngineReadModel;
|
|
7
|
+
}
|
|
8
|
+
export declare function mapTaskStatusFromRunStatus(status: RunSummary["status"], fallback: TaskSummary["status"]): TaskSummary["status"];
|
|
9
|
+
export declare function applyEngineEvent(snapshot: EngineReadModel, event: EngineEvent): EngineEventApplyResult;
|
|
10
|
+
export declare function applyEngineEvents(snapshot: EngineReadModel, events: ReadonlyArray<EngineEvent>): EngineEventApplyResult;
|
|
11
|
+
export declare function pruneQueueEntries(snapshot: EngineReadModel): QueueEntry[];
|
|
12
|
+
export {};
|
|
@@ -149,7 +149,7 @@ function mapLegacySessionStatusToRunStatus(status) {
|
|
|
149
149
|
case "error":
|
|
150
150
|
return "failed";
|
|
151
151
|
case "stopped":
|
|
152
|
-
return "
|
|
152
|
+
return "stopped";
|
|
153
153
|
default:
|
|
154
154
|
return "created";
|
|
155
155
|
}
|
|
@@ -185,12 +185,15 @@ function mapTaskStatusFromRunStatus(status, fallback) {
|
|
|
185
185
|
case "paused":
|
|
186
186
|
return "in_progress";
|
|
187
187
|
case "reviewing":
|
|
188
|
+
case "closing-out":
|
|
188
189
|
return "under_review";
|
|
190
|
+
case "needs-attention":
|
|
191
|
+
return "blocked";
|
|
189
192
|
case "completed":
|
|
190
193
|
return "completed";
|
|
191
194
|
case "failed":
|
|
192
195
|
return "ready";
|
|
193
|
-
case "
|
|
196
|
+
case "stopped":
|
|
194
197
|
return "cancelled";
|
|
195
198
|
}
|
|
196
199
|
}
|
|
@@ -1040,7 +1043,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1040
1043
|
...base,
|
|
1041
1044
|
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1042
1045
|
...run,
|
|
1043
|
-
status: "
|
|
1046
|
+
status: "stopped",
|
|
1044
1047
|
updatedAt: payload.createdAt,
|
|
1045
1048
|
completedAt: payload.createdAt
|
|
1046
1049
|
}))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const RIG_CORE_PACKAGE = "@rig/core";
|
|
2
|
+
export { definePlugin } from "./define-plugin";
|
|
3
|
+
export { defineConfig } from "./define-config";
|
|
4
|
+
export { createPluginHost } from "./plugin-host";
|
|
5
|
+
export type { PluginHost } from "./plugin-host";
|
|
6
|
+
export { buildRigInitConfigSource, type RigInitConfigInput, } from "./rig-init-builder";
|
|
7
|
+
export type { RegisteredValidator, RigPluginRuntime, RigPluginWithRuntime, TaskSourceFactoryContext, TaskSourceFactoryEntry, ValidatorContext, ValidatorResult, } from "./plugin-runtime";
|
|
8
|
+
export { applyEngineEvent, applyEngineEvents, applyEngineEvent as applyRigEvent, pruneQueueEntries, type ApplyStatus, type EngineEventApplyResult, type EngineEventApplyResult as RigEventApplyResult, } from "./engineReadModelReducer";
|
|
9
|
+
export { pickDefaultWorkspaceId, projectRunStatusForTaskGrouping, projectTaskStatusForGrouping, projectTaskStatusWithSessions, normalizeTaskAssigneeFilter, readTaskAssigneeLogins, selectAdhocRuns, selectAdhocRunsForWorkspace, selectApprovalsForRun, selectApprovalsForWorkspace, selectGraphsForWorkspace, selectPendingApprovals, selectPrimaryWorkspace, selectQueueForWorkspace, selectRun, selectRunsByTask, selectRunsForTask, selectRunsForWorkspace, selectTask, selectTasksByStatus, selectTasksByWorkspace, selectTasksForWorkspace, selectTasksGroupedByStatus, selectTasksAssignedTo, selectTasksAssignedToMe, selectUserInputsForRun, selectUserInputsForWorkspace, selectWorkspace, selectWorkspaces, type RigTaskSessionProjection, type RigTaskStatusGroup, type RigTaskStatusGroupingInput, } from "./rigSelectors";
|
|
10
|
+
export { buildTaskReferenceIndex, computeTaskBlockingDepths, computeTaskDependencyBadges, isTaskTerminalStatus, readTaskDependencyRefs, readTaskMetadataStringList, readTaskSourceIssueId, resolveTaskReference, selectNextReadyTaskByPriority, type TaskDependencyBadge, type TaskDependencyBadgeKind, type TaskDependencyBadgeSummary, type TaskDependencyProjection, } from "./taskGraph";
|
|
11
|
+
export { extractTaskCode, extractTaskGroupKey, stripTaskCode, } from "./taskGraphCodes";
|
|
12
|
+
export { buildTaskGraphLayout, type TaskGraphEdge, type TaskGraphLane, type TaskGraphLayout, type TaskGraphNode, type TaskGraphStage, } from "./taskGraphLayout";
|
package/dist/src/index.js
CHANGED
|
@@ -43,11 +43,45 @@ 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
|
+
}
|
|
46
63
|
return { ...validated, __runtime: runtime };
|
|
47
64
|
}
|
|
48
65
|
// packages/core/src/define-config.ts
|
|
49
66
|
import { Schema as Schema2 } from "effect";
|
|
50
67
|
import { RigConfig } from "@rig/contracts";
|
|
68
|
+
function normalizeWorkspaceConfig(raw) {
|
|
69
|
+
const workspace = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : { mainRepo: "." };
|
|
70
|
+
workspace.checkout = workspace.checkout ?? workspace.isolation ?? "worktree";
|
|
71
|
+
workspace.isolation = workspace.isolation ?? workspace.checkout;
|
|
72
|
+
workspace.sandbox = workspace.sandbox ?? "enforce";
|
|
73
|
+
return workspace;
|
|
74
|
+
}
|
|
75
|
+
function applyConfigDefaults(raw) {
|
|
76
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
77
|
+
return raw;
|
|
78
|
+
const record = raw;
|
|
79
|
+
return {
|
|
80
|
+
...record,
|
|
81
|
+
plugins: Array.isArray(record.plugins) ? record.plugins : [],
|
|
82
|
+
workspace: normalizeWorkspaceConfig(record.workspace)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
51
85
|
function defineConfig(cfg) {
|
|
52
86
|
const runtimeByName = new Map;
|
|
53
87
|
const plugins = cfg.plugins ?? [];
|
|
@@ -56,7 +90,7 @@ function defineConfig(cfg) {
|
|
|
56
90
|
runtimeByName.set(plugin.name, plugin.__runtime);
|
|
57
91
|
}
|
|
58
92
|
}
|
|
59
|
-
const decoded = Schema2.decodeUnknownSync(RigConfig)(cfg);
|
|
93
|
+
const decoded = Schema2.decodeUnknownSync(RigConfig)(applyConfigDefaults(cfg));
|
|
60
94
|
const decodedPlugins = decoded.plugins.map((p) => {
|
|
61
95
|
const runtime = runtimeByName.get(p.name);
|
|
62
96
|
if (!runtime)
|
|
@@ -124,6 +158,24 @@ function assertRuntimeMatchesMetadata(plugin) {
|
|
|
124
158
|
}
|
|
125
159
|
}
|
|
126
160
|
}
|
|
161
|
+
const declaredHooks = new Map((plugin.contributes?.hooks ?? []).map((hook) => [hook.id, hook]));
|
|
162
|
+
const runtimeHooks = plugin.__runtime?.hooks;
|
|
163
|
+
for (const hookId of Object.keys(runtimeHooks ?? {})) {
|
|
164
|
+
const metadata = declaredHooks.get(hookId);
|
|
165
|
+
if (!metadata) {
|
|
166
|
+
throw new Error(`plugin "${plugin.name}" typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
|
|
167
|
+
}
|
|
168
|
+
if (metadata.command) {
|
|
169
|
+
throw new Error(`plugin "${plugin.name}" hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (runtimeHooks) {
|
|
173
|
+
for (const hook of declaredHooks.values()) {
|
|
174
|
+
if (!runtimeHooks[hook.id] && !hook.command) {
|
|
175
|
+
throw new Error(`plugin "${plugin.name}" hook metadata "${hook.id}" has no implementation (typed function or command)`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
127
179
|
}
|
|
128
180
|
function createPluginHost(plugins) {
|
|
129
181
|
assertUniquePluginNames(plugins);
|
|
@@ -263,7 +315,7 @@ function buildRigInitConfigSource(input) {
|
|
|
263
315
|
lines.push(` issueUpdates: "lifecycle",`);
|
|
264
316
|
lines.push(` projects: { enabled: false },`);
|
|
265
317
|
lines.push(` },`);
|
|
266
|
-
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations:
|
|
318
|
+
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 },`);
|
|
267
319
|
lines.push(` pr: { mode: "auto", watchChecks: true, autoFixChecks: true, autoFixReview: true },`);
|
|
268
320
|
lines.push(` merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },`);
|
|
269
321
|
lines.push(` issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" },`);
|
|
@@ -422,7 +474,7 @@ function mapLegacySessionStatusToRunStatus(status) {
|
|
|
422
474
|
case "error":
|
|
423
475
|
return "failed";
|
|
424
476
|
case "stopped":
|
|
425
|
-
return "
|
|
477
|
+
return "stopped";
|
|
426
478
|
default:
|
|
427
479
|
return "created";
|
|
428
480
|
}
|
|
@@ -458,12 +510,15 @@ function mapTaskStatusFromRunStatus(status, fallback) {
|
|
|
458
510
|
case "paused":
|
|
459
511
|
return "in_progress";
|
|
460
512
|
case "reviewing":
|
|
513
|
+
case "closing-out":
|
|
461
514
|
return "under_review";
|
|
515
|
+
case "needs-attention":
|
|
516
|
+
return "blocked";
|
|
462
517
|
case "completed":
|
|
463
518
|
return "completed";
|
|
464
519
|
case "failed":
|
|
465
520
|
return "ready";
|
|
466
|
-
case "
|
|
521
|
+
case "stopped":
|
|
467
522
|
return "cancelled";
|
|
468
523
|
}
|
|
469
524
|
}
|
|
@@ -1313,7 +1368,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1313
1368
|
...base,
|
|
1314
1369
|
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1315
1370
|
...run,
|
|
1316
|
-
status: "
|
|
1371
|
+
status: "stopped",
|
|
1317
1372
|
updatedAt: payload.createdAt,
|
|
1318
1373
|
completedAt: payload.createdAt
|
|
1319
1374
|
}))
|
|
@@ -2085,10 +2140,40 @@ function projectTaskStatusForGrouping(status) {
|
|
|
2085
2140
|
case "in_progress":
|
|
2086
2141
|
case "under_review":
|
|
2087
2142
|
return "running";
|
|
2143
|
+
case null:
|
|
2144
|
+
case undefined:
|
|
2145
|
+
case "":
|
|
2146
|
+
return "unknown";
|
|
2088
2147
|
default:
|
|
2089
2148
|
return status;
|
|
2090
2149
|
}
|
|
2091
2150
|
}
|
|
2151
|
+
function projectRunStatusForTaskGrouping(status) {
|
|
2152
|
+
switch (status) {
|
|
2153
|
+
case "created":
|
|
2154
|
+
case "queued":
|
|
2155
|
+
case "preparing":
|
|
2156
|
+
return "queued";
|
|
2157
|
+
case "running":
|
|
2158
|
+
case "waiting-approval":
|
|
2159
|
+
case "waiting-user-input":
|
|
2160
|
+
case "paused":
|
|
2161
|
+
case "validating":
|
|
2162
|
+
case "reviewing":
|
|
2163
|
+
case "closing-out":
|
|
2164
|
+
return "running";
|
|
2165
|
+
case "needs-attention":
|
|
2166
|
+
return "blocked";
|
|
2167
|
+
case "failed":
|
|
2168
|
+
case "stopped":
|
|
2169
|
+
return "ready";
|
|
2170
|
+
case "completed":
|
|
2171
|
+
return "completed";
|
|
2172
|
+
case null:
|
|
2173
|
+
case undefined:
|
|
2174
|
+
return null;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2092
2177
|
var STATUS_PRIORITY = [
|
|
2093
2178
|
"running",
|
|
2094
2179
|
"blocked",
|
|
@@ -2100,11 +2185,31 @@ var STATUS_PRIORITY = [
|
|
|
2100
2185
|
"cancelled",
|
|
2101
2186
|
"completed"
|
|
2102
2187
|
];
|
|
2103
|
-
function
|
|
2104
|
-
|
|
2188
|
+
function taskIdFromSession(session) {
|
|
2189
|
+
return session.taskId ?? session.record.taskId ?? null;
|
|
2190
|
+
}
|
|
2191
|
+
function latestSessionByTask(sessions) {
|
|
2192
|
+
const byTask = new Map;
|
|
2193
|
+
for (const session of sessions) {
|
|
2194
|
+
const taskId = taskIdFromSession(session);
|
|
2195
|
+
if (!taskId)
|
|
2196
|
+
continue;
|
|
2197
|
+
const existing = byTask.get(taskId);
|
|
2198
|
+
if (!existing || (session.lastEventAt ?? "").localeCompare(existing.lastEventAt ?? "") >= 0) {
|
|
2199
|
+
byTask.set(taskId, session);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
return byTask;
|
|
2203
|
+
}
|
|
2204
|
+
function projectTaskStatusWithSessions(task, sessionsByTask = new Map) {
|
|
2205
|
+
const sessionStatus = projectRunStatusForTaskGrouping(sessionsByTask.get(task.id)?.status);
|
|
2206
|
+
return sessionStatus ?? projectTaskStatusForGrouping(task.status);
|
|
2207
|
+
}
|
|
2208
|
+
function groupTasksByProjectedStatus(tasks, sessions = []) {
|
|
2209
|
+
const sessionsByTask = latestSessionByTask(sessions);
|
|
2105
2210
|
const byStatus = new Map;
|
|
2106
2211
|
for (const task of tasks) {
|
|
2107
|
-
const projectedStatus =
|
|
2212
|
+
const projectedStatus = projectTaskStatusWithSessions(task, sessionsByTask);
|
|
2108
2213
|
const group = byStatus.get(projectedStatus);
|
|
2109
2214
|
if (group) {
|
|
2110
2215
|
group.push(task);
|
|
@@ -2128,6 +2233,69 @@ function selectTasksGroupedByStatus(snapshot, workspaceId) {
|
|
|
2128
2233
|
}
|
|
2129
2234
|
return result;
|
|
2130
2235
|
}
|
|
2236
|
+
function isProjectionGroupingInput(value) {
|
|
2237
|
+
return Boolean(value && typeof value === "object" && !("snapshotSequence" in value) && Array.isArray(value.tasks));
|
|
2238
|
+
}
|
|
2239
|
+
function selectTasksGroupedByStatus(input, workspaceId) {
|
|
2240
|
+
if (isProjectionGroupingInput(input)) {
|
|
2241
|
+
const filteredTasks = input.workspaceId ? input.tasks.filter((task) => {
|
|
2242
|
+
const workspaceTask = task;
|
|
2243
|
+
return workspaceTask.workspaceId === input.workspaceId;
|
|
2244
|
+
}) : input.tasks;
|
|
2245
|
+
return groupTasksByProjectedStatus(filteredTasks, input.sessions);
|
|
2246
|
+
}
|
|
2247
|
+
const tasks = selectTasksByWorkspace(input, workspaceId ?? null);
|
|
2248
|
+
return groupTasksByProjectedStatus(tasks, []);
|
|
2249
|
+
}
|
|
2250
|
+
function isObjectRecord(value) {
|
|
2251
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2252
|
+
}
|
|
2253
|
+
function normalizeLogin(value) {
|
|
2254
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
2255
|
+
}
|
|
2256
|
+
function assigneeLoginsFromValue(value) {
|
|
2257
|
+
if (!Array.isArray(value))
|
|
2258
|
+
return [];
|
|
2259
|
+
return value.flatMap((entry) => {
|
|
2260
|
+
if (typeof entry === "string" && entry.trim())
|
|
2261
|
+
return [normalizeLogin(entry)];
|
|
2262
|
+
if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
2263
|
+
return [normalizeLogin(entry.login)];
|
|
2264
|
+
}
|
|
2265
|
+
return [];
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
function normalizeTaskAssigneeFilter(assignee, currentUserLogin) {
|
|
2269
|
+
const trimmed = assignee?.trim();
|
|
2270
|
+
if (!trimmed)
|
|
2271
|
+
return null;
|
|
2272
|
+
if (trimmed === "@me" || trimmed.toLowerCase() === "me") {
|
|
2273
|
+
return currentUserLogin?.trim() ? normalizeLogin(currentUserLogin) : null;
|
|
2274
|
+
}
|
|
2275
|
+
return normalizeLogin(trimmed);
|
|
2276
|
+
}
|
|
2277
|
+
function readTaskAssigneeLogins(task) {
|
|
2278
|
+
const taskRecord = task;
|
|
2279
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2280
|
+
const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
|
|
2281
|
+
return Array.from(new Set([
|
|
2282
|
+
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
2283
|
+
...assigneeLoginsFromValue(metadata?.assignees),
|
|
2284
|
+
...assigneeLoginsFromValue(raw?.assignees)
|
|
2285
|
+
]));
|
|
2286
|
+
}
|
|
2287
|
+
function taskMatchesAssigneeFilter(task, assignee, options = {}) {
|
|
2288
|
+
const normalized = normalizeTaskAssigneeFilter(assignee, options.currentUserLogin);
|
|
2289
|
+
if (!normalized)
|
|
2290
|
+
return false;
|
|
2291
|
+
return readTaskAssigneeLogins(task).includes(normalized);
|
|
2292
|
+
}
|
|
2293
|
+
function selectTasksAssignedTo(tasks, assignee, options = {}) {
|
|
2294
|
+
return tasks.filter((task) => taskMatchesAssigneeFilter(task, assignee, options));
|
|
2295
|
+
}
|
|
2296
|
+
function selectTasksAssignedToMe(tasks, currentUserLogin) {
|
|
2297
|
+
return selectTasksAssignedTo(tasks, "@me", currentUserLogin === undefined ? {} : { currentUserLogin });
|
|
2298
|
+
}
|
|
2131
2299
|
function selectRun(snapshot, runId) {
|
|
2132
2300
|
if (!runId)
|
|
2133
2301
|
return null;
|
|
@@ -2189,17 +2357,45 @@ function selectUserInputsForWorkspace(snapshot, workspaceId) {
|
|
|
2189
2357
|
return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
2190
2358
|
}
|
|
2191
2359
|
// packages/core/src/taskGraph.ts
|
|
2192
|
-
function
|
|
2360
|
+
function isObjectRecord2(value) {
|
|
2193
2361
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2194
2362
|
}
|
|
2195
|
-
function
|
|
2196
|
-
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2197
|
-
const value = metadata?.[key];
|
|
2363
|
+
function readStringList(value) {
|
|
2198
2364
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2199
2365
|
}
|
|
2366
|
+
function unique(values) {
|
|
2367
|
+
return Array.from(new Set(values));
|
|
2368
|
+
}
|
|
2369
|
+
function readTaskMetadataStringList(task, key) {
|
|
2370
|
+
const taskRecord = task;
|
|
2371
|
+
const topLevel = readStringList(taskRecord[key]);
|
|
2372
|
+
if (topLevel.length > 0)
|
|
2373
|
+
return topLevel;
|
|
2374
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2375
|
+
const metadataList = readStringList(metadata?.[key]);
|
|
2376
|
+
if (metadataList.length > 0)
|
|
2377
|
+
return metadataList;
|
|
2378
|
+
if (key === "dependencies") {
|
|
2379
|
+
return readStringList(metadata?.deps);
|
|
2380
|
+
}
|
|
2381
|
+
return [];
|
|
2382
|
+
}
|
|
2383
|
+
function readTaskDependencyRefs(task) {
|
|
2384
|
+
return unique([
|
|
2385
|
+
...readTaskMetadataStringList(task, "dependencies"),
|
|
2386
|
+
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
2387
|
+
]);
|
|
2388
|
+
}
|
|
2200
2389
|
function readTaskSourceIssueId(task) {
|
|
2201
|
-
|
|
2202
|
-
|
|
2390
|
+
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
2391
|
+
return task.sourceIssueId;
|
|
2392
|
+
}
|
|
2393
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2394
|
+
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
2395
|
+
return metadata.sourceIssueId;
|
|
2396
|
+
}
|
|
2397
|
+
const rigMetadata = isObjectRecord2(metadata?._rig) ? metadata._rig : null;
|
|
2398
|
+
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
2203
2399
|
}
|
|
2204
2400
|
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
2205
2401
|
if (tasksById.has(ref))
|
|
@@ -2229,11 +2425,7 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
2229
2425
|
if (!task)
|
|
2230
2426
|
return 0;
|
|
2231
2427
|
stack.add(taskId);
|
|
2232
|
-
const
|
|
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);
|
|
2428
|
+
const blockers = readTaskDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
2237
2429
|
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
2238
2430
|
stack.delete(taskId);
|
|
2239
2431
|
memo.set(taskId, depth);
|
|
@@ -2244,6 +2436,134 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
2244
2436
|
}
|
|
2245
2437
|
return memo;
|
|
2246
2438
|
}
|
|
2439
|
+
function isTaskTerminalStatus(status) {
|
|
2440
|
+
switch (status) {
|
|
2441
|
+
case "closed":
|
|
2442
|
+
case "completed":
|
|
2443
|
+
case "done":
|
|
2444
|
+
case "cancelled":
|
|
2445
|
+
case "canceled":
|
|
2446
|
+
return true;
|
|
2447
|
+
default:
|
|
2448
|
+
return false;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
function isTaskBlockedStatus(status) {
|
|
2452
|
+
return status === "blocked";
|
|
2453
|
+
}
|
|
2454
|
+
function isTaskRunnableStatus(status) {
|
|
2455
|
+
if (status === null || status === undefined || status === "")
|
|
2456
|
+
return true;
|
|
2457
|
+
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
2458
|
+
return false;
|
|
2459
|
+
switch (status) {
|
|
2460
|
+
case "ready":
|
|
2461
|
+
case "open":
|
|
2462
|
+
case "failed":
|
|
2463
|
+
return true;
|
|
2464
|
+
default:
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
function priorityValue(task) {
|
|
2469
|
+
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
2470
|
+
}
|
|
2471
|
+
function computeTaskDependencyBadges(tasks) {
|
|
2472
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
2473
|
+
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
2474
|
+
const dependencyIdsByTask = new Map;
|
|
2475
|
+
const unresolvedRefsByTask = new Map;
|
|
2476
|
+
const blocksByTask = new Map;
|
|
2477
|
+
for (const task of tasks) {
|
|
2478
|
+
const dependencyIds = [];
|
|
2479
|
+
const unresolvedRefs = [];
|
|
2480
|
+
for (const ref of readTaskDependencyRefs(task)) {
|
|
2481
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
2482
|
+
if (dependencyId && dependencyId !== task.id) {
|
|
2483
|
+
dependencyIds.push(dependencyId);
|
|
2484
|
+
const blocks = blocksByTask.get(dependencyId);
|
|
2485
|
+
if (blocks) {
|
|
2486
|
+
blocks.push(task.id);
|
|
2487
|
+
} else {
|
|
2488
|
+
blocksByTask.set(dependencyId, [task.id]);
|
|
2489
|
+
}
|
|
2490
|
+
} else {
|
|
2491
|
+
unresolvedRefs.push(ref);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
2495
|
+
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
2496
|
+
}
|
|
2497
|
+
const summaries = new Map;
|
|
2498
|
+
for (const task of tasks) {
|
|
2499
|
+
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
2500
|
+
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
2501
|
+
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
2502
|
+
const dependency = index.tasksById.get(dependencyId);
|
|
2503
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
2504
|
+
});
|
|
2505
|
+
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
2506
|
+
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
2507
|
+
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
2508
|
+
const badges = [];
|
|
2509
|
+
if (blocked) {
|
|
2510
|
+
badges.push({
|
|
2511
|
+
kind: "blocked",
|
|
2512
|
+
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
2513
|
+
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
2514
|
+
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
2515
|
+
taskIds: blockedBy
|
|
2516
|
+
});
|
|
2517
|
+
} else if (ready) {
|
|
2518
|
+
badges.push({
|
|
2519
|
+
kind: "ready",
|
|
2520
|
+
label: "ready",
|
|
2521
|
+
description: "No open dependencies block this task."
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
2525
|
+
badges.push({
|
|
2526
|
+
kind: "dependency",
|
|
2527
|
+
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
2528
|
+
description: [
|
|
2529
|
+
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
2530
|
+
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
2531
|
+
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
2532
|
+
].filter((part) => part !== null).join(" "),
|
|
2533
|
+
count: dependencyIds.length + blocks.length,
|
|
2534
|
+
taskIds: unique([...dependencyIds, ...blocks])
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
summaries.set(task.id, {
|
|
2538
|
+
taskId: task.id,
|
|
2539
|
+
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
2540
|
+
dependencyIds,
|
|
2541
|
+
unresolvedDependencyRefs,
|
|
2542
|
+
blockedBy,
|
|
2543
|
+
blocks,
|
|
2544
|
+
blocked,
|
|
2545
|
+
ready,
|
|
2546
|
+
dependencyCount: dependencyIds.length,
|
|
2547
|
+
dependentCount: blocks.length,
|
|
2548
|
+
badges
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
return summaries;
|
|
2552
|
+
}
|
|
2553
|
+
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
2554
|
+
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
2555
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
2556
|
+
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) => {
|
|
2557
|
+
const priorityDelta = priorityValue(left) - priorityValue(right);
|
|
2558
|
+
if (priorityDelta !== 0)
|
|
2559
|
+
return priorityDelta;
|
|
2560
|
+
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
2561
|
+
if (createdDelta !== 0)
|
|
2562
|
+
return createdDelta;
|
|
2563
|
+
return left.id.localeCompare(right.id);
|
|
2564
|
+
});
|
|
2565
|
+
return candidates[0] ?? null;
|
|
2566
|
+
}
|
|
2247
2567
|
// packages/core/src/taskGraphCodes.ts
|
|
2248
2568
|
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
2249
2569
|
function extractTaskCode(title) {
|
|
@@ -2283,11 +2603,11 @@ var PALETTE = [
|
|
|
2283
2603
|
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
2284
2604
|
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
2285
2605
|
];
|
|
2286
|
-
function
|
|
2606
|
+
function isObjectRecord3(value) {
|
|
2287
2607
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2288
2608
|
}
|
|
2289
2609
|
function readIssueType(task) {
|
|
2290
|
-
const metadata =
|
|
2610
|
+
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
2291
2611
|
return typeof metadata?.issueType === "string" ? metadata.issueType : null;
|
|
2292
2612
|
}
|
|
2293
2613
|
function isGraphTask(task) {
|
|
@@ -2582,6 +2902,8 @@ export {
|
|
|
2582
2902
|
selectTasksForWorkspace,
|
|
2583
2903
|
selectTasksByWorkspace,
|
|
2584
2904
|
selectTasksByStatus,
|
|
2905
|
+
selectTasksAssignedToMe,
|
|
2906
|
+
selectTasksAssignedTo,
|
|
2585
2907
|
selectTask,
|
|
2586
2908
|
selectRunsForWorkspace,
|
|
2587
2909
|
selectRunsForTask,
|
|
@@ -2590,6 +2912,7 @@ export {
|
|
|
2590
2912
|
selectQueueForWorkspace,
|
|
2591
2913
|
selectPrimaryWorkspace,
|
|
2592
2914
|
selectPendingApprovals,
|
|
2915
|
+
selectNextReadyTaskByPriority,
|
|
2593
2916
|
selectGraphsForWorkspace,
|
|
2594
2917
|
selectApprovalsForWorkspace,
|
|
2595
2918
|
selectApprovalsForRun,
|
|
@@ -2598,13 +2921,21 @@ export {
|
|
|
2598
2921
|
resolveTaskReference,
|
|
2599
2922
|
readTaskSourceIssueId,
|
|
2600
2923
|
readTaskMetadataStringList,
|
|
2924
|
+
readTaskDependencyRefs,
|
|
2925
|
+
readTaskAssigneeLogins,
|
|
2601
2926
|
pruneQueueEntries,
|
|
2927
|
+
projectTaskStatusWithSessions,
|
|
2928
|
+
projectTaskStatusForGrouping,
|
|
2929
|
+
projectRunStatusForTaskGrouping,
|
|
2602
2930
|
pickDefaultWorkspaceId,
|
|
2931
|
+
normalizeTaskAssigneeFilter,
|
|
2932
|
+
isTaskTerminalStatus,
|
|
2603
2933
|
extractTaskGroupKey,
|
|
2604
2934
|
extractTaskCode,
|
|
2605
2935
|
definePlugin,
|
|
2606
2936
|
defineConfig,
|
|
2607
2937
|
createPluginHost,
|
|
2938
|
+
computeTaskDependencyBadges,
|
|
2608
2939
|
computeTaskBlockingDepths,
|
|
2609
2940
|
buildTaskReferenceIndex,
|
|
2610
2941
|
buildTaskGraphLayout,
|