@h-rig/bundle-default-lifecycle 0.0.6-alpha.135 → 0.0.6-alpha.137

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.
@@ -0,0 +1,14 @@
1
+ import { type InProcessCloseoutInput, type InProcessCloseoutResult } from "@rig/runtime/control-plane/native/in-process-closeout";
2
+ export type PipelineCloseoutResult = {
3
+ readonly mode: "pipeline";
4
+ /** The resolved stage order the kernel executed (the journal-recorded pipeline). */
5
+ readonly pipelineStageIds: readonly string[];
6
+ readonly result: InProcessCloseoutResult;
7
+ };
8
+ /**
9
+ * Run the default-lifecycle closeout as a resolved stage pipeline driven by the
10
+ * kernel's stage runner. The lifecycle plugin contributes stage descriptors as
11
+ * metadata and the per-run executor map as runtime; plugin contributions can
12
+ * mutate and execute alongside those defaults.
13
+ */
14
+ export declare function runPipelineCloseout(input: InProcessCloseoutInput): Promise<PipelineCloseoutResult>;
@@ -0,0 +1,633 @@
1
+ // @bun
2
+ // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
3
+ import { resolve } from "path";
4
+ import { loadConfig } from "@rig/core/load-config";
5
+ import { createPluginHost } from "@rig/core";
6
+ import { createDefaultKernel } from "@rig/kernel/default-kernel";
7
+ import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
8
+ import {
9
+ CloseoutValidationError
10
+ } from "@rig/runtime/control-plane/native/in-process-closeout";
11
+ import { taskValidate } from "@rig/runtime/control-plane/native/task-ops";
12
+
13
+ // packages/bundle-default-lifecycle/src/defaultPipeline.ts
14
+ import { resolveKernelStages } from "@rig/kernel/resolver";
15
+ import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
16
+
17
+ // packages/bundle-default-lifecycle/src/stages/auto-merge.ts
18
+ import { runRepoDefaultMerge } from "@rig/runtime/control-plane/native/pr-automation";
19
+
20
+ // packages/bundle-default-lifecycle/src/stages/types.ts
21
+ function defineDefaultLifecycleStage(input) {
22
+ return {
23
+ ...input,
24
+ priority: input.priority ?? 0
25
+ };
26
+ }
27
+
28
+ // packages/bundle-default-lifecycle/src/stages/auto-merge.ts
29
+ var autoMergeStage = defineDefaultLifecycleStage({
30
+ id: "auto-merge",
31
+ kind: "transform",
32
+ description: "Merge an approved PR using the repository default merge method through the runtime helper.",
33
+ calls: ["runRepoDefaultMerge"]
34
+ });
35
+ async function runAutoMergeStage(input) {
36
+ await runRepoDefaultMerge(input);
37
+ }
38
+
39
+ // packages/bundle-default-lifecycle/src/stages/commit.ts
40
+ import { commitRunChanges } from "@rig/runtime/control-plane/native/pr-automation";
41
+ var commitStage = defineDefaultLifecycleStage({
42
+ id: "commit",
43
+ kind: "transform",
44
+ description: "Commit the agent worktree changes using the runtime git closeout helper.",
45
+ calls: ["commitRunChanges"]
46
+ });
47
+ async function runCommitStage(input) {
48
+ return await commitRunChanges(input);
49
+ }
50
+
51
+ // packages/bundle-default-lifecycle/src/stages/isolation.ts
52
+ import { ensureAgentRuntime } from "@rig/runtime/control-plane/runtime/isolation";
53
+ var isolationStage = defineDefaultLifecycleStage({
54
+ id: "isolation",
55
+ kind: "transform",
56
+ description: "Provision the isolated runtime worktree through the runtime isolation subsystem.",
57
+ calls: ["ensureAgentRuntime"]
58
+ });
59
+
60
+ // packages/bundle-default-lifecycle/src/stages/journal-append.ts
61
+ var journalAppendStage = defineDefaultLifecycleStage({
62
+ id: "journal-append",
63
+ kind: "observe",
64
+ description: "Record resolved pipeline and per-stage outcome entries through the kernel journal capability.",
65
+ calls: ["journalCapability.append"]
66
+ });
67
+
68
+ // packages/bundle-default-lifecycle/src/stages/merge-gate.ts
69
+ import {
70
+ runStrictPrMergeGate
71
+ } from "@rig/runtime/control-plane/native/pr-review-gate";
72
+ var mergeGateStage = defineDefaultLifecycleStage({
73
+ id: "merge-gate",
74
+ kind: "gate",
75
+ description: "Enforce GitHub review state, required checks, and configured review gates through runtime PR automation.",
76
+ calls: ["runStrictPrMergeGate"]
77
+ });
78
+ async function runMergeGateStage(input) {
79
+ return await runStrictPrMergeGate({
80
+ projectRoot: input.projectRoot,
81
+ prUrl: input.prUrl,
82
+ taskId: input.taskId,
83
+ runId: input.runId,
84
+ cycle: input.cycle,
85
+ command: input.command,
86
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
87
+ ...input.allowedFailures !== undefined ? { allowedFailures: input.allowedFailures } : {},
88
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
89
+ ...input.final !== undefined ? { final: input.final } : {},
90
+ ...input.requireGreptile !== undefined ? { requireGreptile: input.requireGreptile } : {}
91
+ });
92
+ }
93
+
94
+ // packages/bundle-default-lifecycle/src/stages/open-pr.ts
95
+ import { runPrAutomation } from "@rig/runtime/control-plane/native/pr-automation";
96
+ var openPrStage = defineDefaultLifecycleStage({
97
+ id: "open-pr",
98
+ kind: "transform",
99
+ description: "Open or reuse the closeout PR through the existing runtime PR automation seam.",
100
+ calls: ["runPrAutomation"]
101
+ });
102
+ async function runOpenPrStage(input) {
103
+ return await runPrAutomation({
104
+ projectRoot: input.workspace,
105
+ taskId: input.taskId,
106
+ runId: input.runId,
107
+ branch: input.branch,
108
+ sourceTask: input.sourceTask ? { title: typeof input.sourceTask.title === "string" ? input.sourceTask.title : null } : null,
109
+ command: input.command,
110
+ gitCommand: input.gitCommand,
111
+ steerPi: input.steerPi,
112
+ ...input.config ? { config: input.config } : {},
113
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
114
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
115
+ ...input.lifecycle !== undefined ? { lifecycle: input.lifecycle } : {},
116
+ ...input.uploadedSnapshot !== undefined ? { uploadedSnapshot: input.uploadedSnapshot } : {}
117
+ });
118
+ }
119
+
120
+ // packages/bundle-default-lifecycle/src/stages/push.ts
121
+ import { pushBranchSyncedWithOrigin } from "@rig/runtime/control-plane/native/pr-automation";
122
+ var pushStage = defineDefaultLifecycleStage({
123
+ id: "push",
124
+ kind: "transform",
125
+ description: "Synchronize and push the task branch using the runtime closeout git helper.",
126
+ calls: ["pushBranchSyncedWithOrigin"]
127
+ });
128
+ async function runPushStage(input) {
129
+ await pushBranchSyncedWithOrigin(input);
130
+ }
131
+
132
+ // packages/bundle-default-lifecycle/src/stages/source-closeout.ts
133
+ import { closeIssueAfterMergedPr } from "@rig/runtime/control-plane/native/pr-automation";
134
+ var sourceCloseoutStage = defineDefaultLifecycleStage({
135
+ id: "source-closeout",
136
+ kind: "transform",
137
+ description: "Reflect the merged PR into the task source using the existing runtime closeout helper.",
138
+ calls: ["closeIssueAfterMergedPr"]
139
+ });
140
+ async function runSourceCloseoutStage(input) {
141
+ if (input.pr.status !== "merged" || !input.pr.prUrl)
142
+ return;
143
+ await closeIssueAfterMergedPr({
144
+ projectRoot: input.projectRoot,
145
+ taskId: input.taskId,
146
+ runId: input.runId,
147
+ prUrl: input.pr.prUrl,
148
+ updateTaskSource: input.updateTaskSource,
149
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {}
150
+ });
151
+ }
152
+
153
+ // packages/bundle-default-lifecycle/src/stages/validate.ts
154
+ var validateStage = defineDefaultLifecycleStage({
155
+ id: "validate",
156
+ kind: "transform",
157
+ description: "Run plugin-host validators against the isolated worktree before closeout side effects.",
158
+ calls: ["taskValidate"]
159
+ });
160
+ async function runValidateStage(input, runner) {
161
+ return await runner(input);
162
+ }
163
+
164
+ // packages/bundle-default-lifecycle/src/stages/verify.ts
165
+ var verifyStage = defineDefaultLifecycleStage({
166
+ id: "verify",
167
+ kind: "gate",
168
+ description: "Run the local verifier preflight and block closeout when it rejects the worktree.",
169
+ calls: ["verifierPreflight"]
170
+ });
171
+
172
+ // packages/bundle-default-lifecycle/src/defaultPipeline.ts
173
+ var DEFAULT_STAGE_AFTER = {
174
+ verify: ["validate"],
175
+ commit: ["verify"],
176
+ push: ["commit"],
177
+ "open-pr": ["push"],
178
+ "merge-gate": ["open-pr"],
179
+ "auto-merge": ["merge-gate"],
180
+ "source-closeout": ["auto-merge"],
181
+ isolation: ["source-closeout"],
182
+ "journal-append": ["isolation"]
183
+ };
184
+ function withDefaultAnchors(stage) {
185
+ const after = DEFAULT_STAGE_AFTER[stage.id];
186
+ return after ? { ...stage, after } : stage;
187
+ }
188
+ var defaultLifecycleStages = [
189
+ validateStage,
190
+ verifyStage,
191
+ commitStage,
192
+ pushStage,
193
+ openPrStage,
194
+ mergeGateStage,
195
+ autoMergeStage,
196
+ sourceCloseoutStage,
197
+ isolationStage,
198
+ journalAppendStage
199
+ ].map(withDefaultAnchors);
200
+ function resolveDefaultLifecycle(input = {}) {
201
+ return resolveKernelStages(defaultLifecycleStages, input.mutations ?? []);
202
+ }
203
+ function defaultPipelineShowData(input = {}) {
204
+ const resolved = resolveDefaultLifecycle(input);
205
+ const orderIndex = new Map(resolved.order.map((id, index) => [id, index + 1]));
206
+ const stages = resolved.record.map((entry) => ({
207
+ index: orderIndex.get(entry.stageId) ?? null,
208
+ id: entry.stageId,
209
+ contributedBy: entry.contributedBy,
210
+ ...entry.removedBy !== undefined ? { removedBy: entry.removedBy } : {},
211
+ ...entry.replacedBy !== undefined ? { replacedBy: entry.replacedBy } : {},
212
+ wrappedBy: entry.wrappedBy ?? [],
213
+ droppedAnchors: entry.droppedAnchors ?? []
214
+ }));
215
+ return {
216
+ title: `resolved run pipeline (${resolved.order.length} stages)`,
217
+ stageCount: resolved.order.length,
218
+ stages,
219
+ droppedAnchors: stages.flatMap((stage) => stage.droppedAnchors.map((anchor) => `${stage.id}:${anchor}`)),
220
+ cycles: resolved.cycles,
221
+ ...resolved.resolvedAt !== undefined ? { resolvedAt: resolved.resolvedAt } : {}
222
+ };
223
+ }
224
+ function defaultKernelStatusData() {
225
+ const plugin = createDefaultKernelPlugin();
226
+ const resolved = resolveDefaultLifecycle();
227
+ const capabilities = {};
228
+ for (const capability of plugin.provides)
229
+ capabilities[capability] = plugin.meta.id;
230
+ return {
231
+ kernelProviderId: plugin.meta.id,
232
+ kernelVersion: plugin.meta.version,
233
+ capabilities,
234
+ pipelineStageCount: resolved.order.length,
235
+ stageOrder: [...resolved.order]
236
+ };
237
+ }
238
+ function formatKernelStatus(data = defaultKernelStatusData()) {
239
+ const lines = [
240
+ `kernel: ${data.kernelProviderId} v${data.kernelVersion}`,
241
+ "capabilities (resolved provider):",
242
+ ...Object.entries(data.capabilities).map(([cap, provider]) => ` ${cap.padEnd(14)} ${provider}`),
243
+ `lifecycle stages: mutable`,
244
+ `resolved pipeline (${data.pipelineStageCount} stages): ${data.stageOrder.join(" -> ")}`
245
+ ];
246
+ return lines.join(`
247
+ `);
248
+ }
249
+ function formatDefaultPipelineShow(input = {}) {
250
+ const data = defaultPipelineShowData(input);
251
+ const lines = [`${data.title}:`];
252
+ for (const stage of data.stages) {
253
+ if (stage.removedBy) {
254
+ lines.push(` - ${stage.id.padEnd(20)} [${stage.contributedBy}] removed by [${stage.removedBy}]`);
255
+ continue;
256
+ }
257
+ const ordinal = stage.index === null ? " -." : `${String(stage.index).padStart(2)}.`;
258
+ const annotations = [
259
+ stage.replacedBy ? `replaced by [${stage.replacedBy}]` : "",
260
+ stage.wrappedBy.length > 0 ? `wrapped by [${stage.wrappedBy.join(", ")}]` : ""
261
+ ].filter(Boolean);
262
+ lines.push(` ${ordinal} ${stage.id.padEnd(20)} [${stage.contributedBy}]${annotations.length > 0 ? ` ${annotations.join(" ")}` : ""}`);
263
+ }
264
+ lines.push(`dropped anchors: ${data.droppedAnchors.length > 0 ? data.droppedAnchors.join(", ") : "none"} cycles: ${data.cycles.length > 0 ? data.cycles.map((cycle) => cycle.join(" -> ")).join(", ") : "none"}`);
265
+ return lines.join(`
266
+ `);
267
+ }
268
+
269
+ // packages/bundle-default-lifecycle/src/plugin.ts
270
+ import { definePlugin } from "@rig/core";
271
+
272
+ // packages/bundle-default-lifecycle/src/cli.ts
273
+ var DEFAULT_PIPELINE_CLI_ID = "default-lifecycle.pipeline";
274
+ var DEFAULT_KERNEL_CLI_ID = "default-lifecycle.kernel";
275
+ function printJson(value) {
276
+ console.log(JSON.stringify(value, null, 2));
277
+ }
278
+ function takeFlag(args, flag) {
279
+ const rest = [...args];
280
+ const index = rest.indexOf(flag);
281
+ if (index < 0)
282
+ return { value: false, rest };
283
+ rest.splice(index, 1);
284
+ return { value: true, rest };
285
+ }
286
+ function requireNoExtraArgs(args, usage) {
287
+ if (args.length > 0)
288
+ throw new Error(`Unexpected argument: ${args[0]}
289
+ Usage: ${usage}`);
290
+ }
291
+ async function executePipeline(context, args) {
292
+ const [first = "show", ...rest] = args;
293
+ const command = first.startsWith("-") ? "show" : first;
294
+ const commandArgs = first.startsWith("-") ? args : rest;
295
+ if (command !== "show")
296
+ throw new Error(`Unknown pipeline command: ${command}`);
297
+ const json = takeFlag(commandArgs, "--json");
298
+ requireNoExtraArgs(json.rest, "rig pipeline show [--json]");
299
+ const details = defaultPipelineShowData();
300
+ if (context.outputMode === "text") {
301
+ if (json.value)
302
+ printJson(details);
303
+ else
304
+ console.log(formatDefaultPipelineShow());
305
+ }
306
+ return { ok: true, group: "pipeline", command: "show", details };
307
+ }
308
+ async function executeKernel(context, args) {
309
+ const [first = "status", ...rest] = args;
310
+ const command = first.startsWith("-") ? "status" : first;
311
+ const commandArgs = first.startsWith("-") ? args : rest;
312
+ if (command !== "status")
313
+ throw new Error(`Unknown kernel command: ${command}`);
314
+ const json = takeFlag(commandArgs, "--json");
315
+ requireNoExtraArgs(json.rest, "rig kernel status [--json]");
316
+ const details = defaultKernelStatusData();
317
+ if (context.outputMode === "text") {
318
+ if (json.value)
319
+ printJson(details);
320
+ else
321
+ console.log(formatKernelStatus(details));
322
+ }
323
+ return { ok: true, group: "kernel", command: "status", details };
324
+ }
325
+ var defaultLifecycleCliCommands = [
326
+ {
327
+ id: DEFAULT_PIPELINE_CLI_ID,
328
+ family: "pipeline",
329
+ command: "rig pipeline show [--json]",
330
+ description: "Show the resolved default lifecycle pipeline.",
331
+ usage: "rig pipeline show [--json]",
332
+ run: executePipeline
333
+ },
334
+ {
335
+ id: DEFAULT_KERNEL_CLI_ID,
336
+ family: "kernel",
337
+ command: "rig kernel status [--json]",
338
+ description: "Show default kernel provider and lifecycle status.",
339
+ usage: "rig kernel status [--json]",
340
+ run: executeKernel
341
+ }
342
+ ];
343
+
344
+ // packages/bundle-default-lifecycle/src/plugin.ts
345
+ var DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
346
+ function createDefaultLifecyclePlugin(stages = {}) {
347
+ const plugin = definePlugin({
348
+ name: DEFAULT_LIFECYCLE_PLUGIN_ID,
349
+ version: "0.0.0-alpha.1",
350
+ provides: [],
351
+ contributes: {
352
+ stages: defaultLifecycleStages,
353
+ capabilities: [
354
+ { id: "default-lifecycle.pipeline", title: "Default lifecycle pipeline", commandId: DEFAULT_PIPELINE_CLI_ID },
355
+ { id: "default-lifecycle.kernel-status", title: "Default kernel status", commandId: DEFAULT_KERNEL_CLI_ID }
356
+ ],
357
+ cliCommands: defaultLifecycleCliCommands.map(({ run: _run, ...metadata }) => metadata)
358
+ }
359
+ }, {
360
+ stages,
361
+ featureCapabilities: [
362
+ { id: "default-lifecycle.pipeline", title: "Default lifecycle pipeline", commandId: DEFAULT_PIPELINE_CLI_ID },
363
+ { id: "default-lifecycle.kernel-status", title: "Default kernel status", commandId: DEFAULT_KERNEL_CLI_ID }
364
+ ],
365
+ cliCommands: defaultLifecycleCliCommands
366
+ });
367
+ return {
368
+ ...plugin,
369
+ meta: { id: DEFAULT_LIFECYCLE_PLUGIN_ID, name: "Default Rig Lifecycle", version: "0.0.0-alpha.1" },
370
+ contributes: { ...plugin.contributes ?? {}, stages: defaultLifecycleStages },
371
+ runtime: { stages }
372
+ };
373
+ }
374
+ var defaultLifecyclePlugin = createDefaultLifecyclePlugin();
375
+
376
+ // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
377
+ function cleanString(value) {
378
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
379
+ }
380
+ function closeoutOutcome(status) {
381
+ switch (status) {
382
+ case "completed":
383
+ return "completed";
384
+ case "failed":
385
+ case "needs-attention":
386
+ return "failed";
387
+ case "pending":
388
+ case "running":
389
+ return "started";
390
+ }
391
+ }
392
+ async function loadRigAutomationConfig(projectRoot) {
393
+ try {
394
+ return await loadConfig(projectRoot);
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
399
+ async function runRigProjectValidation({ projectRoot, taskId }) {
400
+ const pluginHostCtx = await buildPluginHostContext(projectRoot);
401
+ return taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
402
+ }
403
+ function shouldAttemptRigMerge(config) {
404
+ const mode = config.merge?.mode;
405
+ return mode !== "off" && mode !== "pr-ready";
406
+ }
407
+ async function loadPluginStageContributions(projectRoot) {
408
+ try {
409
+ const config = await loadConfig(projectRoot);
410
+ const host = createPluginHost(config.plugins ?? []);
411
+ return { executors: host.listStageExecutors(), mutations: host.listStageMutations() };
412
+ } catch {
413
+ return { executors: {}, mutations: [] };
414
+ }
415
+ }
416
+ async function runPipelineCloseout(input) {
417
+ const taskId = cleanString(input.taskId);
418
+ if (!taskId) {
419
+ throw new Error("Pipeline closeout requires a task id.");
420
+ }
421
+ const loadedConfig = input.config ?? await loadRigAutomationConfig(input.projectRoot);
422
+ const prMode = loadedConfig?.pr?.mode ?? "off";
423
+ const reviewProvider = loadedConfig?.review?.provider ?? "github";
424
+ const effectiveConfig = {
425
+ ...loadedConfig ?? {},
426
+ pr: { ...loadedConfig?.pr ?? {}, mode: prMode },
427
+ review: { ...loadedConfig?.review ?? {}, provider: reviewProvider }
428
+ };
429
+ const openOnlyConfig = {
430
+ ...effectiveConfig,
431
+ merge: { ...effectiveConfig.merge ?? {}, mode: "pr-ready" }
432
+ };
433
+ const shouldMerge = shouldAttemptRigMerge(effectiveConfig);
434
+ const workspace = input.workspace;
435
+ const artifactRoot = input.artifactRoot ?? resolve(input.projectRoot, "artifacts", taskId);
436
+ const journal = async (phase, status, detail) => {
437
+ await input.journalPhase(phase, closeoutOutcome(status), detail ?? null);
438
+ };
439
+ if (prMode === "off" || prMode === "ask") {
440
+ const reason = prMode === "ask" ? "PR creation awaits operator approval." : "PR automation disabled.";
441
+ await input.reflect("under_review", reason);
442
+ await journal("completed", "completed", reason);
443
+ return {
444
+ mode: "pipeline",
445
+ pipelineStageIds: [...resolveDefaultLifecycle().order],
446
+ result: { status: "skipped", iterations: 0, feedback: [] }
447
+ };
448
+ }
449
+ const state = {
450
+ branch: input.branch,
451
+ validationPassed: false,
452
+ committed: false,
453
+ pushed: false,
454
+ prUrl: null,
455
+ prReady: false,
456
+ pr: null,
457
+ gate: null,
458
+ mergeGate: null,
459
+ merged: false,
460
+ iterations: 0,
461
+ feedback: [],
462
+ blockedDetail: null
463
+ };
464
+ const cont = (ctx2) => ({ kind: "continue", ctx: ctx2 });
465
+ const executors = {
466
+ isolation: (ctx2) => cont(ctx2),
467
+ validate: async (ctx2) => {
468
+ await input.onValidationStart?.();
469
+ await journal("queued", "running", `Validating task ${taskId} before closeout.`);
470
+ let passed = false;
471
+ try {
472
+ passed = await runValidateStage({ projectRoot: input.projectRoot, taskId }, input.runValidation ?? runRigProjectValidation);
473
+ } catch (error) {
474
+ const detail = `Rig validation failed before closeout: ${error instanceof Error ? error.message : String(error)}`;
475
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
476
+ throw new CloseoutValidationError(detail);
477
+ }
478
+ if (!passed) {
479
+ const detail = `Rig validation failed for task ${taskId}; closeout blocked before commit.`;
480
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
481
+ throw new CloseoutValidationError(detail);
482
+ }
483
+ state.validationPassed = true;
484
+ await journal("queued", "completed", `Validation passed for task ${taskId}.`);
485
+ const workspaceBranch = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
486
+ const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? cleanString(workspaceBranch.stdout) : null;
487
+ if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== state.branch) {
488
+ state.branch = currentWorkspaceBranch;
489
+ }
490
+ return cont(ctx2);
491
+ },
492
+ verify: () => state.validationPassed ? { kind: "allow" } : { kind: "block", reason: "validation did not pass" },
493
+ commit: async (ctx2) => {
494
+ await journal("commit", "running", `Committing changes in ${workspace}.`);
495
+ const committed = await runCommitStage({ cwd: workspace, message: `rig: complete task ${taskId}`, command: input.gitCommand });
496
+ state.committed = committed.committed;
497
+ return cont(ctx2);
498
+ },
499
+ push: async (ctx2) => {
500
+ await journal("push", "running", `Pushing branch ${state.branch}.`);
501
+ await runPushStage({ projectRoot: workspace, branch: state.branch, gitCommand: input.gitCommand });
502
+ state.pushed = true;
503
+ return cont(ctx2);
504
+ },
505
+ "open-pr": async (ctx2) => {
506
+ await journal("pr-review-merge", "running", `Opening a pull request for ${state.branch}.`);
507
+ const pr = await runOpenPrStage({
508
+ ...input,
509
+ taskId,
510
+ branch: state.branch,
511
+ artifactRoot,
512
+ config: openOnlyConfig,
513
+ sourceTask: { title: cleanString(input.sourceTask?.title) },
514
+ lifecycle: {
515
+ onPrOpened: async ({ prUrl }) => {
516
+ await journal("pr-opened", "running", prUrl);
517
+ await input.reflect("under_review", "Rig opened a pull request for this task.");
518
+ },
519
+ onFeedback: async ({ feedback }) => {
520
+ await input.reflect("ci_fixing", "Rig is fixing CI/review feedback for this task.", { errorText: feedback.join(`
521
+ `) || null });
522
+ }
523
+ }
524
+ });
525
+ state.pr = pr;
526
+ state.prUrl = pr.prUrl ?? null;
527
+ state.prReady = pr.status === "opened" || pr.status === "merged";
528
+ state.iterations = pr.iterations;
529
+ state.feedback = [...pr.actionableFeedback];
530
+ if (pr.status === "needs_attention") {
531
+ state.blockedDetail = pr.actionableFeedback.join(`
532
+ `) || "PR automation did not produce a mergeable PR.";
533
+ }
534
+ return cont(ctx2);
535
+ },
536
+ "merge-gate": async (ctx2) => {
537
+ if (!shouldMerge || !state.prReady || !state.prUrl) {
538
+ state.mergeGate = state.prReady ? "skipped" : null;
539
+ return state.prReady ? cont(ctx2) : { kind: "block", reason: state.blockedDetail ?? "no mergeable PR to gate" };
540
+ }
541
+ const gate = await runMergeGateStage({
542
+ projectRoot: workspace,
543
+ prUrl: state.prUrl,
544
+ taskId,
545
+ runId: input.runId,
546
+ cycle: 1,
547
+ command: input.command,
548
+ artifactRoot,
549
+ final: true,
550
+ ...effectiveConfig.merge?.allowedFailures ? { allowedFailures: effectiveConfig.merge.allowedFailures } : {},
551
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
552
+ requireGreptile: reviewProvider === "greptile"
553
+ });
554
+ state.gate = gate;
555
+ if (gate.approved) {
556
+ state.mergeGate = "passed";
557
+ return cont(ctx2);
558
+ }
559
+ state.mergeGate = "blocked";
560
+ const detail = gate.actionableFeedback.join(`
561
+ `) || gate.reasons.join("; ") || "merge gate blocked the PR.";
562
+ state.blockedDetail = detail;
563
+ await input.reflect("needs_attention", "Rig needs operator attention before this task can merge.", { errorText: detail });
564
+ await journal("pr-review-merge", "needs-attention", detail);
565
+ return { kind: "block", reason: detail };
566
+ },
567
+ "auto-merge": async (ctx2) => {
568
+ if (!shouldMerge || state.mergeGate !== "passed" || !state.prUrl || !state.gate) {
569
+ return cont(ctx2);
570
+ }
571
+ await journal("merge", "running", state.prUrl);
572
+ await input.reflect("merging", "Rig is merging the pull request for this task.");
573
+ await runAutoMergeStage({ prUrl: state.prUrl, config: effectiveConfig, command: input.command, cwd: workspace, strictGate: state.gate });
574
+ state.merged = true;
575
+ return cont(ctx2);
576
+ },
577
+ "source-closeout": async (ctx2) => {
578
+ if (!state.merged || !state.prUrl)
579
+ return cont(ctx2);
580
+ await journal("close-source", "running", state.prUrl);
581
+ const mergedPr = { ...state.pr ?? { iterations: state.iterations, actionableFeedback: state.feedback }, status: "merged", prUrl: state.prUrl, merged: true };
582
+ await runSourceCloseoutStage({
583
+ projectRoot: input.projectRoot,
584
+ taskId,
585
+ runId: input.runId,
586
+ pr: mergedPr,
587
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
588
+ updateTaskSource: async () => {
589
+ await input.reflect("closed", "Rig merged the pull request and closed this task source.");
590
+ return { updated: true, taskId, status: "closed", source: "runtime", sourceKind: "runtime" };
591
+ }
592
+ });
593
+ return cont(ctx2);
594
+ },
595
+ "journal-append": (ctx2) => cont(ctx2)
596
+ };
597
+ const defaultLifecyclePlugin2 = createDefaultLifecyclePlugin(executors);
598
+ const pluginStages = await loadPluginStageContributions(input.projectRoot);
599
+ const kernel = createDefaultKernel({ ...input.kernelJournal ? { journal: input.kernelJournal } : {}, stageExecutors: { ...defaultLifecyclePlugin2.runtime.stages, ...pluginStages.executors } });
600
+ const resolved = kernel.stageRunner.resolve(defaultLifecyclePlugin2.contributes.stages, pluginStages.mutations);
601
+ const ctx = {
602
+ runId: input.runId,
603
+ taskId,
604
+ state,
605
+ metadata: { projectRoot: input.projectRoot, workspace, closeoutState: state }
606
+ };
607
+ await kernel.stageRunner.runPipeline(input.runId, resolved, ctx);
608
+ const result = mapStateToResult(state);
609
+ if (result.status === "merged" || result.status === "opened") {
610
+ await journal("completed", "completed", result.prUrl ? `${result.status === "merged" ? "PR merged and issue closed" : "PR ready without merge"}: ${result.prUrl}` : result.status);
611
+ }
612
+ return { mode: "pipeline", pipelineStageIds: [...resolved.order], result };
613
+ }
614
+ function mapStateToResult(state) {
615
+ if (state.merged && state.prUrl) {
616
+ return { status: "merged", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
617
+ }
618
+ if (state.mergeGate === "blocked" || state.blockedDetail) {
619
+ return {
620
+ status: "needs-attention",
621
+ ...state.prUrl ? { prUrl: state.prUrl } : {},
622
+ iterations: state.iterations,
623
+ feedback: state.feedback
624
+ };
625
+ }
626
+ if (state.prReady && state.prUrl) {
627
+ return { status: "opened", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
628
+ }
629
+ return { status: "needs-attention", ...state.prUrl ? { prUrl: state.prUrl } : {}, iterations: state.iterations, feedback: state.feedback };
630
+ }
631
+ export {
632
+ runPipelineCloseout
633
+ };
@@ -1,18 +1,19 @@
1
- import type { CapabilityTag } from "@rig/contracts";
1
+ import { type RigPluginWithRuntime } from "@rig/core";
2
+ import type { StageRun } from "@rig/contracts";
2
3
  import { defaultLifecycleStages } from "./defaultPipeline";
3
4
  export declare const DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
4
- export type DefaultLifecyclePlugin = {
5
+ export type DefaultLifecyclePlugin = RigPluginWithRuntime & {
5
6
  readonly meta: {
6
7
  readonly id: typeof DEFAULT_LIFECYCLE_PLUGIN_ID;
7
8
  readonly name: string;
8
9
  readonly version: string;
9
10
  };
10
- readonly provides: ReadonlySet<CapabilityTag>;
11
- readonly contributes: {
11
+ readonly contributes: NonNullable<RigPluginWithRuntime["contributes"]> & {
12
12
  readonly stages: typeof defaultLifecycleStages;
13
13
  };
14
14
  readonly runtime: {
15
- readonly stages: typeof defaultLifecycleStages;
15
+ readonly stages: Readonly<Record<string, StageRun>>;
16
16
  };
17
17
  };
18
- export declare function createDefaultLifecyclePlugin(): DefaultLifecyclePlugin;
18
+ export declare function createDefaultLifecyclePlugin(stages?: Readonly<Record<string, StageRun>>): DefaultLifecyclePlugin;
19
+ export declare const defaultLifecyclePlugin: DefaultLifecyclePlugin;