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

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.
@@ -3,7 +3,7 @@ import { type KernelStageResolverResult } from "@rig/kernel/resolver";
3
3
  import type { DefaultLifecycleStageDescriptor, DefaultLifecycleStageId } from "./stages/types";
4
4
  export declare const DEFAULT_LIFECYCLE_STAGE_IDS: readonly ["validate", "verify", "commit", "push", "open-pr", "merge-gate", "auto-merge", "source-closeout", "isolation", "journal-append"];
5
5
  export declare const PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS: readonly ["merge-gate", "isolation", "journal-append"];
6
- export declare const defaultLifecycleStages: readonly [DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor, DefaultLifecycleStageDescriptor];
6
+ export declare const defaultLifecycleStages: readonly DefaultLifecycleStageDescriptor[];
7
7
  export type ResolveDefaultLifecycleInput = {
8
8
  readonly mutations?: readonly StageMutation[];
9
9
  readonly protectedStageGrants?: readonly ProtectedStageGrant[];
@@ -29,4 +29,20 @@ export type PipelineShowData = {
29
29
  readonly resolvedAt?: string;
30
30
  };
31
31
  export declare function defaultPipelineShowData(input?: ResolveDefaultLifecycleInput): PipelineShowData;
32
+ export type KernelStatusData = {
33
+ readonly kernelProviderId: string;
34
+ readonly kernelVersion: string;
35
+ readonly capabilities: Readonly<Record<string, string>>;
36
+ readonly protectedStages: readonly string[];
37
+ readonly pipelineStageCount: number;
38
+ readonly stageOrder: readonly string[];
39
+ };
40
+ /**
41
+ * Data backing `rig kernel status`: the resolved provider per kernel capability,
42
+ * the kernel version, the protected-stage set, and the resolved default
43
+ * pipeline. Derived from the default kernel plugin so it reflects what actually
44
+ * boots, not a hardcoded list.
45
+ */
46
+ export declare function defaultKernelStatusData(): KernelStatusData;
47
+ export declare function formatKernelStatus(data?: KernelStatusData): string;
32
48
  export declare function formatDefaultPipelineShow(input?: ResolveDefaultLifecycleInput): string;
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // packages/bundle-default-lifecycle/src/defaultPipeline.ts
3
3
  import { resolveKernelStages } from "@rig/kernel/resolver";
4
+ import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
4
5
 
5
6
  // packages/bundle-default-lifecycle/src/stages/auto-merge.ts
6
7
  import { runRepoDefaultMerge } from "@rig/runtime/control-plane/native/pr-automation";
@@ -51,6 +52,9 @@ var journalAppendStage = defineDefaultLifecycleStage({
51
52
  });
52
53
 
53
54
  // packages/bundle-default-lifecycle/src/stages/merge-gate.ts
55
+ import {
56
+ runStrictPrMergeGate
57
+ } from "@rig/runtime/control-plane/native/pr-review-gate";
54
58
  var mergeGateStage = defineDefaultLifecycleStage({
55
59
  id: "merge-gate",
56
60
  kind: "gate",
@@ -116,6 +120,21 @@ var DEFAULT_LIFECYCLE_STAGE_IDS = [
116
120
  "journal-append"
117
121
  ];
118
122
  var PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS = ["merge-gate", "isolation", "journal-append"];
123
+ var DEFAULT_STAGE_AFTER = {
124
+ verify: ["validate"],
125
+ commit: ["verify"],
126
+ push: ["commit"],
127
+ "open-pr": ["push"],
128
+ "merge-gate": ["open-pr"],
129
+ "auto-merge": ["merge-gate"],
130
+ "source-closeout": ["auto-merge"],
131
+ isolation: ["source-closeout"],
132
+ "journal-append": ["isolation"]
133
+ };
134
+ function withDefaultAnchors(stage) {
135
+ const after = DEFAULT_STAGE_AFTER[stage.id];
136
+ return after ? { ...stage, after } : stage;
137
+ }
119
138
  var defaultLifecycleStages = [
120
139
  validateStage,
121
140
  verifyStage,
@@ -127,7 +146,7 @@ var defaultLifecycleStages = [
127
146
  sourceCloseoutStage,
128
147
  isolationStage,
129
148
  journalAppendStage
130
- ];
149
+ ].map(withDefaultAnchors);
131
150
  function defaultLifecycleStageById(id) {
132
151
  const stage = defaultLifecycleStages.find((candidate) => candidate.id === id);
133
152
  if (!stage)
@@ -160,6 +179,32 @@ function defaultPipelineShowData(input = {}) {
160
179
  ...resolved.resolvedAt !== undefined ? { resolvedAt: resolved.resolvedAt } : {}
161
180
  };
162
181
  }
182
+ function defaultKernelStatusData() {
183
+ const plugin = createDefaultKernelPlugin();
184
+ const resolved = resolveDefaultLifecycle();
185
+ const capabilities = {};
186
+ for (const capability of plugin.provides)
187
+ capabilities[capability] = plugin.meta.id;
188
+ return {
189
+ kernelProviderId: plugin.meta.id,
190
+ kernelVersion: plugin.meta.version,
191
+ capabilities,
192
+ protectedStages: [...PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS],
193
+ pipelineStageCount: resolved.order.length,
194
+ stageOrder: [...resolved.order]
195
+ };
196
+ }
197
+ function formatKernelStatus(data = defaultKernelStatusData()) {
198
+ const lines = [
199
+ `kernel: ${data.kernelProviderId} v${data.kernelVersion}`,
200
+ "capabilities (resolved provider):",
201
+ ...Object.entries(data.capabilities).map(([cap, provider]) => ` ${cap.padEnd(14)} ${provider}`),
202
+ `protected stages: ${data.protectedStages.join(", ")}`,
203
+ `resolved pipeline (${data.pipelineStageCount} stages): ${data.stageOrder.join(" -> ")}`
204
+ ];
205
+ return lines.join(`
206
+ `);
207
+ }
163
208
  function formatDefaultPipelineShow(input = {}) {
164
209
  const data = defaultPipelineShowData(input);
165
210
  const lines = [`${data.title}:`];
@@ -182,10 +227,12 @@ function formatDefaultPipelineShow(input = {}) {
182
227
  }
183
228
  export {
184
229
  resolveDefaultLifecycle,
230
+ formatKernelStatus,
185
231
  formatDefaultPipelineShow,
186
232
  defaultPipelineShowData,
187
233
  defaultLifecycleStages,
188
234
  defaultLifecycleStageById,
235
+ defaultKernelStatusData,
189
236
  PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS,
190
237
  DEFAULT_LIFECYCLE_STAGE_IDS
191
238
  };
@@ -1,7 +1,7 @@
1
1
  export * from "./closeoutEquivalence";
2
2
  export * from "./closeoutShadowHarness";
3
3
  export * from "./defaultPipeline";
4
- export * from "./stagedCloseout";
4
+ export * from "./pipelineCloseout";
5
5
  export * from "./plugin";
6
6
  export * from "./stages/auto-merge";
7
7
  export * from "./stages/commit";
package/dist/src/index.js CHANGED
@@ -96,6 +96,7 @@ function assertCloseoutShadowEquivalent(input) {
96
96
  }
97
97
  // packages/bundle-default-lifecycle/src/defaultPipeline.ts
98
98
  import { resolveKernelStages } from "@rig/kernel/resolver";
99
+ import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
99
100
 
100
101
  // packages/bundle-default-lifecycle/src/stages/auto-merge.ts
101
102
  import { runRepoDefaultMerge } from "@rig/runtime/control-plane/native/pr-automation";
@@ -158,6 +159,9 @@ async function runJournalAppendStage(input) {
158
159
  }
159
160
 
160
161
  // packages/bundle-default-lifecycle/src/stages/merge-gate.ts
162
+ import {
163
+ runStrictPrMergeGate
164
+ } from "@rig/runtime/control-plane/native/pr-review-gate";
161
165
  var mergeGateStage = defineDefaultLifecycleStage({
162
166
  id: "merge-gate",
163
167
  kind: "gate",
@@ -165,6 +169,21 @@ var mergeGateStage = defineDefaultLifecycleStage({
165
169
  calls: ["runStrictPrMergeGate"],
166
170
  protected: true
167
171
  });
172
+ async function runMergeGateStage(input) {
173
+ return await runStrictPrMergeGate({
174
+ projectRoot: input.projectRoot,
175
+ prUrl: input.prUrl,
176
+ taskId: input.taskId,
177
+ runId: input.runId,
178
+ cycle: input.cycle,
179
+ command: input.command,
180
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
181
+ ...input.allowedFailures !== undefined ? { allowedFailures: input.allowedFailures } : {},
182
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
183
+ ...input.final !== undefined ? { final: input.final } : {},
184
+ ...input.requireGreptile !== undefined ? { requireGreptile: input.requireGreptile } : {}
185
+ });
186
+ }
168
187
 
169
188
  // packages/bundle-default-lifecycle/src/stages/open-pr.ts
170
189
  import { runPrAutomation } from "@rig/runtime/control-plane/native/pr-automation";
@@ -261,6 +280,21 @@ var DEFAULT_LIFECYCLE_STAGE_IDS = [
261
280
  "journal-append"
262
281
  ];
263
282
  var PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS = ["merge-gate", "isolation", "journal-append"];
283
+ var DEFAULT_STAGE_AFTER = {
284
+ verify: ["validate"],
285
+ commit: ["verify"],
286
+ push: ["commit"],
287
+ "open-pr": ["push"],
288
+ "merge-gate": ["open-pr"],
289
+ "auto-merge": ["merge-gate"],
290
+ "source-closeout": ["auto-merge"],
291
+ isolation: ["source-closeout"],
292
+ "journal-append": ["isolation"]
293
+ };
294
+ function withDefaultAnchors(stage) {
295
+ const after = DEFAULT_STAGE_AFTER[stage.id];
296
+ return after ? { ...stage, after } : stage;
297
+ }
264
298
  var defaultLifecycleStages = [
265
299
  validateStage,
266
300
  verifyStage,
@@ -272,7 +306,7 @@ var defaultLifecycleStages = [
272
306
  sourceCloseoutStage,
273
307
  isolationStage,
274
308
  journalAppendStage
275
- ];
309
+ ].map(withDefaultAnchors);
276
310
  function defaultLifecycleStageById(id) {
277
311
  const stage = defaultLifecycleStages.find((candidate) => candidate.id === id);
278
312
  if (!stage)
@@ -305,6 +339,32 @@ function defaultPipelineShowData(input = {}) {
305
339
  ...resolved.resolvedAt !== undefined ? { resolvedAt: resolved.resolvedAt } : {}
306
340
  };
307
341
  }
342
+ function defaultKernelStatusData() {
343
+ const plugin = createDefaultKernelPlugin();
344
+ const resolved = resolveDefaultLifecycle();
345
+ const capabilities = {};
346
+ for (const capability of plugin.provides)
347
+ capabilities[capability] = plugin.meta.id;
348
+ return {
349
+ kernelProviderId: plugin.meta.id,
350
+ kernelVersion: plugin.meta.version,
351
+ capabilities,
352
+ protectedStages: [...PROTECTED_DEFAULT_LIFECYCLE_STAGE_IDS],
353
+ pipelineStageCount: resolved.order.length,
354
+ stageOrder: [...resolved.order]
355
+ };
356
+ }
357
+ function formatKernelStatus(data = defaultKernelStatusData()) {
358
+ const lines = [
359
+ `kernel: ${data.kernelProviderId} v${data.kernelVersion}`,
360
+ "capabilities (resolved provider):",
361
+ ...Object.entries(data.capabilities).map(([cap, provider]) => ` ${cap.padEnd(14)} ${provider}`),
362
+ `protected stages: ${data.protectedStages.join(", ")}`,
363
+ `resolved pipeline (${data.pipelineStageCount} stages): ${data.stageOrder.join(" -> ")}`
364
+ ];
365
+ return lines.join(`
366
+ `);
367
+ }
308
368
  function formatDefaultPipelineShow(input = {}) {
309
369
  const data = defaultPipelineShowData(input);
310
370
  const lines = [`${data.title}:`];
@@ -325,11 +385,15 @@ function formatDefaultPipelineShow(input = {}) {
325
385
  return lines.join(`
326
386
  `);
327
387
  }
328
- // packages/bundle-default-lifecycle/src/stagedCloseout.ts
388
+ // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
329
389
  import { resolve as resolve2 } from "path";
330
390
  import { loadConfig } from "@rig/core/load-config";
391
+ import { createPluginHost } from "@rig/core";
392
+ import { createDefaultKernel } from "@rig/kernel/default-kernel";
331
393
  import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
332
- import { CloseoutValidationError } from "@rig/runtime/control-plane/native/in-process-closeout";
394
+ import {
395
+ CloseoutValidationError
396
+ } from "@rig/runtime/control-plane/native/in-process-closeout";
333
397
  import { taskValidate } from "@rig/runtime/control-plane/native/task-ops";
334
398
  function cleanString(value) {
335
399
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
@@ -346,9 +410,6 @@ function closeoutOutcome(status) {
346
410
  return "started";
347
411
  }
348
412
  }
349
- function normalizePrStatus(status) {
350
- return status === "needs_attention" ? "needs-attention" : status;
351
- }
352
413
  async function loadRigAutomationConfig(projectRoot) {
353
414
  try {
354
415
  return await loadConfig(projectRoot);
@@ -360,130 +421,232 @@ async function runRigProjectValidation({ projectRoot, taskId }) {
360
421
  const pluginHostCtx = await buildPluginHostContext(projectRoot);
361
422
  return taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
362
423
  }
363
- async function executeStagedCloseout(input) {
424
+ function shouldAttemptRigMerge(config) {
425
+ const mode = config.merge?.mode;
426
+ return mode !== "off" && mode !== "pr-ready";
427
+ }
428
+ async function loadPluginStageContributions(projectRoot) {
429
+ try {
430
+ const config = await loadConfig(projectRoot);
431
+ const host = createPluginHost(config.plugins ?? []);
432
+ return { executors: host.listStageExecutors(), mutations: host.listStageMutations() };
433
+ } catch {
434
+ return { executors: {}, mutations: [] };
435
+ }
436
+ }
437
+ async function runPipelineCloseout(input) {
364
438
  const taskId = cleanString(input.taskId);
365
439
  if (!taskId) {
366
- throw new Error("Staged closeout requires a task id.");
440
+ throw new Error("Pipeline closeout requires a task id.");
367
441
  }
368
442
  const loadedConfig = input.config ?? await loadRigAutomationConfig(input.projectRoot);
369
443
  const prMode = loadedConfig?.pr?.mode ?? "off";
370
444
  const reviewProvider = loadedConfig?.review?.provider ?? "github";
371
445
  const effectiveConfig = {
372
446
  ...loadedConfig ?? {},
373
- pr: {
374
- ...loadedConfig?.pr ?? {},
375
- mode: prMode
376
- },
377
- review: {
378
- ...loadedConfig?.review ?? {},
379
- provider: reviewProvider
380
- }
447
+ pr: { ...loadedConfig?.pr ?? {}, mode: prMode },
448
+ review: { ...loadedConfig?.review ?? {}, provider: reviewProvider }
449
+ };
450
+ const openOnlyConfig = {
451
+ ...effectiveConfig,
452
+ merge: { ...effectiveConfig.merge ?? {}, mode: "pr-ready" }
381
453
  };
454
+ const shouldMerge = shouldAttemptRigMerge(effectiveConfig);
382
455
  const workspace = input.workspace;
383
456
  const artifactRoot = input.artifactRoot ?? resolve2(input.projectRoot, "artifacts", taskId);
384
- let branch = input.branch;
385
- let currentPhase = "queued";
386
457
  const journal = async (phase, status, detail) => {
387
- currentPhase = phase;
388
458
  await input.journalPhase(phase, closeoutOutcome(status), detail ?? null);
389
459
  };
390
- const complete = async (result, detail) => {
391
- await journal("completed", "completed", detail);
392
- return result;
460
+ if (prMode === "off" || prMode === "ask") {
461
+ const reason = prMode === "ask" ? "PR creation awaits operator approval." : "PR automation disabled.";
462
+ await input.reflect("under_review", reason);
463
+ await journal("completed", "completed", reason);
464
+ return {
465
+ mode: "pipeline",
466
+ pipelineStageIds: [...resolveDefaultLifecycle().order],
467
+ result: { status: "skipped", iterations: 0, feedback: [] }
468
+ };
469
+ }
470
+ const state = {
471
+ branch: input.branch,
472
+ validationPassed: false,
473
+ committed: false,
474
+ pushed: false,
475
+ prUrl: null,
476
+ prReady: false,
477
+ pr: null,
478
+ gate: null,
479
+ mergeGate: null,
480
+ merged: false,
481
+ iterations: 0,
482
+ feedback: [],
483
+ blockedDetail: null
393
484
  };
394
- try {
395
- if (prMode === "off" || prMode === "ask") {
396
- const reason = prMode === "ask" ? "PR creation awaits operator approval." : "PR automation disabled.";
397
- await input.reflect("under_review", reason);
398
- return await complete({ status: "skipped", iterations: 0, feedback: [] }, reason);
399
- }
400
- await input.onValidationStart?.();
401
- await journal("queued", "running", `Validating task ${taskId} before closeout.`);
402
- let validationPassed = false;
403
- try {
404
- validationPassed = await runValidateStage({ projectRoot: input.projectRoot, taskId }, input.runValidation ?? runRigProjectValidation);
405
- } catch (error) {
406
- const detail2 = `Rig validation failed before closeout: ${error instanceof Error ? error.message : String(error)}`;
407
- await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail2 });
408
- throw new CloseoutValidationError(detail2);
409
- }
410
- if (!validationPassed) {
411
- const detail2 = `Rig validation failed for task ${taskId}; closeout blocked before commit.`;
412
- await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail2 });
413
- throw new CloseoutValidationError(detail2);
414
- }
415
- await journal("queued", "completed", `Validation passed for task ${taskId}.`);
416
- const workspaceBranch = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
417
- const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? cleanString(workspaceBranch.stdout) : null;
418
- if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== branch) {
419
- branch = currentWorkspaceBranch;
420
- }
421
- await journal("commit", "running", `Committing changes in ${workspace}.`);
422
- await runCommitStage({ cwd: workspace, message: `rig: complete task ${taskId}`, command: input.gitCommand });
423
- await journal("push", "running", `Pushing branch ${branch}.`);
424
- await runPushStage({ projectRoot: workspace, branch, gitCommand: input.gitCommand });
425
- await journal("pr-review-merge", "running", `Opening and merging a pull request for ${branch}.`);
426
- const pr = await runOpenPrStage({
427
- ...input,
428
- taskId,
429
- branch,
430
- artifactRoot,
431
- config: effectiveConfig,
432
- sourceTask: { title: cleanString(input.sourceTask?.title) },
433
- lifecycle: {
434
- onPrOpened: async ({ prUrl }) => {
435
- await journal("pr-opened", "running", prUrl);
436
- await input.reflect("under_review", "Rig opened a pull request for this task.");
437
- },
438
- onFeedback: async ({ feedback }) => {
439
- await input.reflect("ci_fixing", "Rig is fixing CI/review feedback for this task.", { errorText: feedback.join(`
485
+ const cont = (ctx2) => ({ kind: "continue", ctx: ctx2 });
486
+ const executors = {
487
+ isolation: (ctx2) => cont(ctx2),
488
+ validate: async (ctx2) => {
489
+ await input.onValidationStart?.();
490
+ await journal("queued", "running", `Validating task ${taskId} before closeout.`);
491
+ let passed = false;
492
+ try {
493
+ passed = await runValidateStage({ projectRoot: input.projectRoot, taskId }, input.runValidation ?? runRigProjectValidation);
494
+ } catch (error) {
495
+ const detail = `Rig validation failed before closeout: ${error instanceof Error ? error.message : String(error)}`;
496
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
497
+ throw new CloseoutValidationError(detail);
498
+ }
499
+ if (!passed) {
500
+ const detail = `Rig validation failed for task ${taskId}; closeout blocked before commit.`;
501
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
502
+ throw new CloseoutValidationError(detail);
503
+ }
504
+ state.validationPassed = true;
505
+ await journal("queued", "completed", `Validation passed for task ${taskId}.`);
506
+ const workspaceBranch = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
507
+ const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? cleanString(workspaceBranch.stdout) : null;
508
+ if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== state.branch) {
509
+ state.branch = currentWorkspaceBranch;
510
+ }
511
+ return cont(ctx2);
512
+ },
513
+ verify: () => state.validationPassed ? { kind: "allow" } : { kind: "block", reason: "validation did not pass" },
514
+ commit: async (ctx2) => {
515
+ await journal("commit", "running", `Committing changes in ${workspace}.`);
516
+ const committed = await runCommitStage({ cwd: workspace, message: `rig: complete task ${taskId}`, command: input.gitCommand });
517
+ state.committed = committed.committed;
518
+ return cont(ctx2);
519
+ },
520
+ push: async (ctx2) => {
521
+ await journal("push", "running", `Pushing branch ${state.branch}.`);
522
+ await runPushStage({ projectRoot: workspace, branch: state.branch, gitCommand: input.gitCommand });
523
+ state.pushed = true;
524
+ return cont(ctx2);
525
+ },
526
+ "open-pr": async (ctx2) => {
527
+ await journal("pr-review-merge", "running", `Opening a pull request for ${state.branch}.`);
528
+ const pr = await runOpenPrStage({
529
+ ...input,
530
+ taskId,
531
+ branch: state.branch,
532
+ artifactRoot,
533
+ config: openOnlyConfig,
534
+ sourceTask: { title: cleanString(input.sourceTask?.title) },
535
+ lifecycle: {
536
+ onPrOpened: async ({ prUrl }) => {
537
+ await journal("pr-opened", "running", prUrl);
538
+ await input.reflect("under_review", "Rig opened a pull request for this task.");
539
+ },
540
+ onFeedback: async ({ feedback }) => {
541
+ await input.reflect("ci_fixing", "Rig is fixing CI/review feedback for this task.", { errorText: feedback.join(`
440
542
  `) || null });
441
- },
442
- onMergeStarted: async ({ prUrl }) => {
443
- await journal("merge", "running", prUrl);
444
- await input.reflect("merging", "Rig is merging the pull request for this task.");
445
- },
446
- onMerged: async ({ prUrl }) => {
447
- await journal("close-source", "running", prUrl);
543
+ }
448
544
  }
545
+ });
546
+ state.pr = pr;
547
+ state.prUrl = pr.prUrl ?? null;
548
+ state.prReady = pr.status === "opened" || pr.status === "merged";
549
+ state.iterations = pr.iterations;
550
+ state.feedback = [...pr.actionableFeedback];
551
+ if (pr.status === "needs_attention") {
552
+ state.blockedDetail = pr.actionableFeedback.join(`
553
+ `) || "PR automation did not produce a mergeable PR.";
554
+ }
555
+ return cont(ctx2);
556
+ },
557
+ "merge-gate": async (ctx2) => {
558
+ if (!shouldMerge || !state.prReady || !state.prUrl) {
559
+ state.mergeGate = state.prReady ? "skipped" : null;
560
+ return state.prReady ? cont(ctx2) : { kind: "block", reason: state.blockedDetail ?? "no mergeable PR to gate" };
561
+ }
562
+ const gate = await runMergeGateStage({
563
+ projectRoot: workspace,
564
+ prUrl: state.prUrl,
565
+ taskId,
566
+ runId: input.runId,
567
+ cycle: 1,
568
+ command: input.command,
569
+ artifactRoot,
570
+ final: true,
571
+ ...effectiveConfig.merge?.allowedFailures ? { allowedFailures: effectiveConfig.merge.allowedFailures } : {},
572
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
573
+ requireGreptile: reviewProvider === "greptile"
574
+ });
575
+ state.gate = gate;
576
+ if (gate.approved) {
577
+ state.mergeGate = "passed";
578
+ return cont(ctx2);
579
+ }
580
+ state.mergeGate = "blocked";
581
+ const detail = gate.actionableFeedback.join(`
582
+ `) || gate.reasons.join("; ") || "merge gate blocked the PR.";
583
+ state.blockedDetail = detail;
584
+ await input.reflect("needs_attention", "Rig needs operator attention before this task can merge.", { errorText: detail });
585
+ await journal("pr-review-merge", "needs-attention", detail);
586
+ return { kind: "block", reason: detail };
587
+ },
588
+ "auto-merge": async (ctx2) => {
589
+ if (!shouldMerge || state.mergeGate !== "passed" || !state.prUrl || !state.gate) {
590
+ return cont(ctx2);
449
591
  }
450
- });
451
- if (pr.status === "merged" && pr.prUrl) {
592
+ await journal("merge", "running", state.prUrl);
593
+ await input.reflect("merging", "Rig is merging the pull request for this task.");
594
+ await runAutoMergeStage({ prUrl: state.prUrl, config: effectiveConfig, command: input.command, cwd: workspace, strictGate: state.gate });
595
+ state.merged = true;
596
+ return cont(ctx2);
597
+ },
598
+ "source-closeout": async (ctx2) => {
599
+ if (!state.merged || !state.prUrl)
600
+ return cont(ctx2);
601
+ await journal("close-source", "running", state.prUrl);
602
+ const mergedPr = { ...state.pr ?? { iterations: state.iterations, actionableFeedback: state.feedback }, status: "merged", prUrl: state.prUrl, merged: true };
452
603
  await runSourceCloseoutStage({
453
604
  projectRoot: input.projectRoot,
454
605
  taskId,
455
606
  runId: input.runId,
456
- pr,
607
+ pr: mergedPr,
457
608
  ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
458
609
  updateTaskSource: async () => {
459
610
  await input.reflect("closed", "Rig merged the pull request and closed this task source.");
460
611
  return { updated: true, taskId, status: "closed", source: "runtime", sourceKind: "runtime" };
461
612
  }
462
613
  });
463
- return await complete({ status: "merged", prUrl: pr.prUrl, iterations: pr.iterations, feedback: pr.actionableFeedback }, `PR merged and issue closed: ${pr.prUrl}`);
464
- }
465
- if (pr.status === "opened" && pr.prUrl) {
466
- return await complete({ status: "opened", prUrl: pr.prUrl, iterations: pr.iterations, feedback: pr.actionableFeedback }, `PR ready without merge: ${pr.prUrl}`);
467
- }
468
- const detail = pr.actionableFeedback.join(`
469
- `) || "PR automation did not merge the PR.";
470
- await input.reflect("needs_attention", "Rig needs operator attention before this task can proceed.", { errorText: detail });
471
- await journal("pr-review-merge", "needs-attention", detail);
472
- return { status: normalizePrStatus(pr.status), ...pr.prUrl ? { prUrl: pr.prUrl } : {}, iterations: pr.iterations, feedback: pr.actionableFeedback };
473
- } catch (error) {
474
- const detail = error instanceof Error ? error.message : String(error);
475
- await journal(currentPhase, "failed", detail);
476
- throw error;
614
+ return cont(ctx2);
615
+ },
616
+ "journal-append": (ctx2) => cont(ctx2)
617
+ };
618
+ const pluginStages = await loadPluginStageContributions(input.projectRoot);
619
+ const kernel = createDefaultKernel({ stageExecutors: { ...executors, ...pluginStages.executors } });
620
+ const resolved = kernel.stageRunner.resolve(defaultLifecycleStages, pluginStages.mutations);
621
+ const ctx = {
622
+ runId: input.runId,
623
+ taskId,
624
+ state,
625
+ metadata: { projectRoot: input.projectRoot, workspace }
626
+ };
627
+ await kernel.stageRunner.runPipeline(input.runId, resolved, ctx);
628
+ const result = mapStateToResult(state);
629
+ if (result.status === "merged" || result.status === "opened") {
630
+ await journal("completed", "completed", result.prUrl ? `${result.status === "merged" ? "PR merged and issue closed" : "PR ready without merge"}: ${result.prUrl}` : result.status);
477
631
  }
632
+ return { mode: "pipeline", pipelineStageIds: [...resolved.order], result };
478
633
  }
479
- async function runStagedCloseout(input) {
480
- const resolved = resolveDefaultLifecycle();
481
- const result = await executeStagedCloseout(input);
482
- return {
483
- mode: "staged",
484
- pipelineStageIds: [...resolved.order],
485
- result
486
- };
634
+ function mapStateToResult(state) {
635
+ if (state.merged && state.prUrl) {
636
+ return { status: "merged", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
637
+ }
638
+ if (state.mergeGate === "blocked" || state.blockedDetail) {
639
+ return {
640
+ status: "needs-attention",
641
+ ...state.prUrl ? { prUrl: state.prUrl } : {},
642
+ iterations: state.iterations,
643
+ feedback: state.feedback
644
+ };
645
+ }
646
+ if (state.prReady && state.prUrl) {
647
+ return { status: "opened", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
648
+ }
649
+ return { status: "needs-attention", ...state.prUrl ? { prUrl: state.prUrl } : {}, iterations: state.iterations, feedback: state.feedback };
487
650
  }
488
651
  // packages/bundle-default-lifecycle/src/plugin.ts
489
652
  var DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
@@ -502,10 +665,11 @@ export {
502
665
  snapshotCloseoutArtifacts,
503
666
  runVerifyStage,
504
667
  runValidateStage,
505
- runStagedCloseout,
506
668
  runSourceCloseoutStage,
507
669
  runPushStage,
670
+ runPipelineCloseout,
508
671
  runOpenPrStage,
672
+ runMergeGateStage,
509
673
  runJournalAppendStage,
510
674
  runIsolationStage,
511
675
  runCommitStage,
@@ -516,11 +680,13 @@ export {
516
680
  mergeGateStage,
517
681
  journalAppendStage,
518
682
  isolationStage,
683
+ formatKernelStatus,
519
684
  formatDefaultPipelineShow,
520
685
  defineDefaultLifecycleStage,
521
686
  defaultPipelineShowData,
522
687
  defaultLifecycleStages,
523
688
  defaultLifecycleStageById,
689
+ defaultKernelStatusData,
524
690
  createDefaultLifecyclePlugin,
525
691
  compareCloseoutShadowRuns,
526
692
  compareCloseoutEquivalence,
@@ -0,0 +1,17 @@
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. Each kernel stage executor invokes the corresponding
11
+ * `run<Stage>` wrapper against the shared closeout state, so `open-pr`,
12
+ * `merge-gate`, and `auto-merge` are discrete executed stages (the protected
13
+ * `merge-gate` runs `runStrictPrMergeGate` as a real gate, blocking the
14
+ * pipeline when the PR is not approved). This is the single closeout path; the
15
+ * earlier parallel imperative `runStagedCloseout` copy is retired.
16
+ */
17
+ export declare function runPipelineCloseout(input: InProcessCloseoutInput): Promise<PipelineCloseoutResult>;