@h-rig/bundle-default-lifecycle 0.0.6-alpha.154 → 0.0.6-alpha.156

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.
Files changed (37) hide show
  1. package/dist/src/branch-naming.d.ts +15 -0
  2. package/dist/src/branch-naming.js +33 -0
  3. package/dist/src/cli.js +4 -4
  4. package/dist/src/closeoutEquivalence.d.ts +1 -1
  5. package/dist/src/control-plane/completion-verification.d.ts +19 -0
  6. package/dist/src/control-plane/completion-verification.js +1917 -0
  7. package/dist/src/control-plane/pr-automation.d.ts +87 -0
  8. package/dist/src/control-plane/pr-automation.js +638 -0
  9. package/dist/src/control-plane/task-verify.d.ts +1 -0
  10. package/dist/src/control-plane/task-verify.js +1484 -0
  11. package/dist/src/control-plane/verifier.d.ts +138 -0
  12. package/dist/src/control-plane/verifier.js +1478 -0
  13. package/dist/src/defaultPipeline.js +4 -4
  14. package/dist/src/index.js +2716 -54
  15. package/dist/src/native/closeout-runners.d.ts +5 -0
  16. package/dist/src/native/closeout-runners.js +41 -0
  17. package/dist/src/native/in-process-closeout.d.ts +40 -0
  18. package/dist/src/native/in-process-closeout.js +802 -0
  19. package/dist/src/pipelineCloseout.d.ts +1 -1
  20. package/dist/src/pipelineCloseout.js +2712 -52
  21. package/dist/src/plugin.d.ts +4 -17
  22. package/dist/src/plugin.js +2029 -25
  23. package/dist/src/stages/auto-merge.d.ts +1 -2
  24. package/dist/src/stages/auto-merge.js +657 -3
  25. package/dist/src/stages/commit.d.ts +1 -1
  26. package/dist/src/stages/commit.js +657 -3
  27. package/dist/src/stages/isolation.js +3 -2
  28. package/dist/src/stages/merge-gate.d.ts +1 -2
  29. package/dist/src/stages/merge-gate.js +1 -1
  30. package/dist/src/stages/open-pr.d.ts +2 -2
  31. package/dist/src/stages/open-pr.js +657 -3
  32. package/dist/src/stages/push.d.ts +1 -1
  33. package/dist/src/stages/push.js +657 -3
  34. package/dist/src/stages/source-closeout.d.ts +1 -1
  35. package/dist/src/stages/source-closeout.js +657 -3
  36. package/dist/src/stages/validate.d.ts +1 -1
  37. package/package.json +32 -5
@@ -1,5 +1,659 @@
1
1
  // @bun
2
- var __require = import.meta.require;
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // packages/bundle-default-lifecycle/src/control-plane/pr-automation.ts
19
+ var exports_pr_automation = {};
20
+ __export(exports_pr_automation, {
21
+ runRepoDefaultMerge: () => runRepoDefaultMerge,
22
+ runPrAutomation: () => runPrAutomation,
23
+ resolvePrAutomationLimits: () => resolvePrAutomationLimits,
24
+ requestGreptileRereview: () => requestGreptileRereview,
25
+ pushBranchSyncedWithOrigin: () => pushBranchSyncedWithOrigin,
26
+ hasCommittableRunChanges: () => hasCommittableRunChanges,
27
+ gateNeedsGreptileRereview: () => gateNeedsGreptileRereview,
28
+ commitRunChanges: () => commitRunChanges,
29
+ collectPendingPrChecks: () => collectPendingPrChecks,
30
+ collectActionablePrFeedback: () => collectActionablePrFeedback,
31
+ closeIssueAfterMergedPr: () => closeIssueAfterMergedPr,
32
+ buildPrAutomationBody: () => buildPrAutomationBody,
33
+ UPLOADED_SNAPSHOT_PR_MARKER: () => UPLOADED_SNAPSHOT_PR_MARKER
34
+ });
35
+ import { assertSafeGitBranchName } from "@rig/shared/safe-identifiers";
36
+ import { runStrictPrMergeGate } from "@rig/pr-review-plugin";
37
+ import {
38
+ strictMergeHeadShaFromGate
39
+ } from "@rig/contracts";
40
+ function positiveInt(value, fallback) {
41
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
42
+ }
43
+ function resolvePrAutomationLimits(config) {
44
+ return {
45
+ maxPrFixIterations: positiveInt(config?.automation?.maxPrFixIterations, 100500)
46
+ };
47
+ }
48
+ function buildPrAutomationBody(input) {
49
+ const lines = [
50
+ input.summary?.trim() || "Rig completed this task autonomously.",
51
+ "",
52
+ `Run: ${input.runId}`
53
+ ];
54
+ if (/^\d+$/.test(input.taskId)) {
55
+ lines.push("", `Closes #${input.taskId}`);
56
+ }
57
+ if (input.uploadedSnapshot) {
58
+ lines.push("", UPLOADED_SNAPSHOT_PR_MARKER, "This PR was seeded from an uploaded local working-tree snapshot. Review local snapshot provenance before merging if repository policy requires it.");
59
+ }
60
+ return lines.join(`
61
+ `);
62
+ }
63
+ function wildcardToRegExp(pattern) {
64
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
65
+ return new RegExp(`^${escaped}$`, "i");
66
+ }
67
+ function isAllowedFailure(name, allowedFailures) {
68
+ return allowedFailures.some((pattern) => wildcardToRegExp(pattern).test(name));
69
+ }
70
+ function isPendingCheck(check) {
71
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
72
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
73
+ return ["pending", "queued", "in_progress", "waiting", "requested", "expected", "action_required"].includes(conclusion) || ["pending", "queued", "in_progress", "waiting", "requested", "expected"].includes(state);
74
+ }
75
+ function isPassingCheck(check) {
76
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
77
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
78
+ return ["success", "successful", "passed", "neutral", "skipped"].includes(conclusion) || ["success", "successful", "passed", "completed"].includes(state);
79
+ }
80
+ function isFailingCheck(check) {
81
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
82
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
83
+ return ["failure", "failed", "timed_out", "action_required", "cancelled", "error"].includes(conclusion) || ["failure", "failed", "timed_out", "action_required", "cancelled", "error"].includes(state);
84
+ }
85
+ function collectPendingPrChecks(input) {
86
+ const allowedFailures = input.allowedFailures ?? [];
87
+ const pending = [];
88
+ for (const check of input.checks ?? []) {
89
+ const name = check.name.trim();
90
+ if (!name || isAllowedFailure(name, allowedFailures))
91
+ continue;
92
+ if (isPendingCheck(check) && !isPassingCheck(check))
93
+ pending.push(name);
94
+ }
95
+ return pending;
96
+ }
97
+ function collectActionablePrFeedback(input) {
98
+ const allowedFailures = input.allowedFailures ?? [];
99
+ const feedback = [];
100
+ for (const check of input.checks ?? []) {
101
+ const name = check.name.trim();
102
+ if (!name || !isFailingCheck(check) || isAllowedFailure(name, allowedFailures))
103
+ continue;
104
+ feedback.push(`Check failed: ${name}${check.detailsUrl ? ` (${check.detailsUrl})` : ""}`);
105
+ }
106
+ for (const thread of input.reviewThreads ?? []) {
107
+ if (thread.resolved === true)
108
+ continue;
109
+ const body = thread.body.trim();
110
+ if (!body)
111
+ continue;
112
+ feedback.push(`Review feedback from ${thread.author?.trim() || "reviewer"}: ${body}`);
113
+ }
114
+ return feedback;
115
+ }
116
+ function parseJsonArray(value) {
117
+ if (!value?.trim())
118
+ return [];
119
+ try {
120
+ const parsed = JSON.parse(value);
121
+ return Array.isArray(parsed) ? parsed : [];
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+ function parsePrChecks(value) {
127
+ return parseJsonArray(value).flatMap((entry) => {
128
+ if (!entry || typeof entry !== "object")
129
+ return [];
130
+ const record = entry;
131
+ const name = typeof record.name === "string" ? record.name : "";
132
+ if (!name.trim())
133
+ return [];
134
+ return [{
135
+ name,
136
+ status: typeof record.status === "string" ? record.status : null,
137
+ state: typeof record.state === "string" ? record.state : null,
138
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
139
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
140
+ }];
141
+ });
142
+ }
143
+ function parsePrViewStatusCheckRollup(value) {
144
+ if (!value?.trim())
145
+ return [];
146
+ try {
147
+ const parsed = JSON.parse(value);
148
+ const rollup = Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : [];
149
+ return rollup.flatMap((entry) => {
150
+ if (!entry || typeof entry !== "object")
151
+ return [];
152
+ const record = entry;
153
+ const name = typeof record.name === "string" ? record.name : "";
154
+ if (!name.trim())
155
+ return [];
156
+ return [{
157
+ name,
158
+ status: typeof record.status === "string" ? record.status : null,
159
+ state: typeof record.state === "string" ? record.state : null,
160
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
161
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
162
+ }];
163
+ });
164
+ } catch {
165
+ return [];
166
+ }
167
+ }
168
+ async function readPrChecks(input) {
169
+ const checks = await input.command(["pr", "checks", input.prUrl, "--json", "name,state,link"], input.cwd ? { cwd: input.cwd } : undefined);
170
+ if (checks.exitCode === 0) {
171
+ return parsePrChecks(checks.stdout);
172
+ }
173
+ const combined = `${checks.stderr ?? ""}
174
+ ${checks.stdout ?? ""}`;
175
+ if (!/unknown flag.*--json|unknown flag: --json|unknown shorthand flag/i.test(combined)) {
176
+ throw new Error(`gh pr checks ${input.prUrl} --json name,state,link failed (${checks.exitCode}): ${checks.stderr ?? checks.stdout ?? ""}`.trim());
177
+ }
178
+ const view = await runChecked(input.command, ["pr", "view", input.prUrl, "--json", "statusCheckRollup"], input.cwd, "gh");
179
+ return parsePrViewStatusCheckRollup(view.stdout);
180
+ }
181
+ function parsePrViewReviewThreads(value) {
182
+ if (!value?.trim())
183
+ return [];
184
+ try {
185
+ const parsed = JSON.parse(value);
186
+ const feedback = [];
187
+ const reviewThreads = parsed.reviewThreads;
188
+ if (Array.isArray(reviewThreads)) {
189
+ feedback.push(...reviewThreads.flatMap((entry) => {
190
+ if (!entry || typeof entry !== "object")
191
+ return [];
192
+ const record = entry;
193
+ const body = typeof record.body === "string" ? record.body : null;
194
+ if (!body)
195
+ return [];
196
+ return [{
197
+ id: typeof record.id === "string" ? record.id : null,
198
+ body,
199
+ resolved: typeof record.resolved === "boolean" ? record.resolved : false,
200
+ author: typeof record.author === "string" ? record.author : null
201
+ }];
202
+ }));
203
+ }
204
+ const reviews = parsed.reviews;
205
+ if (Array.isArray(reviews)) {
206
+ feedback.push(...reviews.flatMap((entry) => {
207
+ if (!entry || typeof entry !== "object")
208
+ return [];
209
+ const record = entry;
210
+ const state = typeof record.state === "string" ? record.state.toUpperCase() : "";
211
+ if (state !== "CHANGES_REQUESTED")
212
+ return [];
213
+ const body = typeof record.body === "string" && record.body.trim() ? record.body : "Changes requested by reviewer.";
214
+ const author = record.author && typeof record.author === "object" ? record.author.login : null;
215
+ return [{
216
+ id: typeof record.id === "string" ? record.id : null,
217
+ body,
218
+ resolved: false,
219
+ author: typeof author === "string" ? author : null
220
+ }];
221
+ }));
222
+ }
223
+ const reviewDecision = typeof parsed.reviewDecision === "string" ? parsed.reviewDecision.toUpperCase() : "";
224
+ if (reviewDecision === "CHANGES_REQUESTED" && feedback.length === 0) {
225
+ feedback.push({ body: "Changes requested by reviewer.", resolved: false });
226
+ }
227
+ return feedback;
228
+ } catch {
229
+ return [];
230
+ }
231
+ }
232
+ function findPrUrl(output) {
233
+ return output?.split(/\s+/).map((part) => part.trim()).find((part) => /^https?:\/\/\S+\/pull\/\d+$/i.test(part)) ?? null;
234
+ }
235
+ function normalizePrUrl(stdout) {
236
+ const url = findPrUrl(stdout);
237
+ if (!url)
238
+ throw new Error("gh pr create did not return a PR URL");
239
+ return url;
240
+ }
241
+ function parseGitHubPullRequestUrl(prUrl) {
242
+ try {
243
+ const parsed = new URL(prUrl);
244
+ const [owner, repo, kind, number] = parsed.pathname.split("/").filter(Boolean);
245
+ if (!owner || !repo || kind !== "pull" || !number || !/^\d+$/.test(number))
246
+ return null;
247
+ return { owner, repo, number };
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
252
+ async function ensureExistingPrBodyHasRigMarkers(input) {
253
+ const view = await input.command(["pr", "view", input.prUrl, "--json", "body"], input.cwd ? { cwd: input.cwd } : undefined);
254
+ if (view.exitCode !== 0) {
255
+ throw new Error(`gh pr view ${input.prUrl} --json body failed (${view.exitCode}): ${view.stderr ?? view.stdout ?? ""}`.trim());
256
+ }
257
+ let currentBody = "";
258
+ try {
259
+ const parsed = JSON.parse(view.stdout ?? "{}");
260
+ currentBody = typeof parsed.body === "string" ? parsed.body : "";
261
+ } catch {
262
+ currentBody = "";
263
+ }
264
+ const requiredBlocks = input.body.split(/\n{2,}/).map((block) => block.trim()).filter((block) => /^Run: /i.test(block) || /^Closes #\d+/i.test(block));
265
+ const missing = requiredBlocks.filter((block) => {
266
+ if (/^Run: /i.test(block))
267
+ return !/^Run: \S+/im.test(currentBody);
268
+ return !currentBody.includes(block);
269
+ });
270
+ if (missing.length === 0)
271
+ return;
272
+ const nextBody = [currentBody.trim(), ...missing].filter(Boolean).join(`
273
+
274
+ `);
275
+ const pr = parseGitHubPullRequestUrl(input.prUrl);
276
+ if (!pr)
277
+ throw new Error(`Cannot update existing PR body for unrecognized PR URL: ${input.prUrl}`);
278
+ const edit = await input.command(["api", `repos/${pr.owner}/${pr.repo}/issues/${pr.number}`, "-X", "PATCH", "-f", `body=${nextBody}`], input.cwd ? { cwd: input.cwd } : undefined);
279
+ if (edit.exitCode !== 0) {
280
+ throw new Error(`gh api repos/${pr.owner}/${pr.repo}/issues/${pr.number} -X PATCH -f body=<redacted> failed (${edit.exitCode}): ${edit.stderr ?? edit.stdout ?? ""}`.trim());
281
+ }
282
+ }
283
+ async function runChecked(command, args, cwd, label = "gh") {
284
+ const result = await command(args, cwd ? { cwd } : undefined);
285
+ if (result.exitCode !== 0) {
286
+ throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
287
+ }
288
+ return result;
289
+ }
290
+ function statusPathFromShortLine(line) {
291
+ const rawPath = line.length > 3 ? line.slice(3).trim() : "";
292
+ const renamedPath = rawPath.includes(" -> ") ? rawPath.split(" -> ").at(-1).trim() : rawPath;
293
+ if (renamedPath.startsWith('"') && renamedPath.endsWith('"')) {
294
+ try {
295
+ return JSON.parse(renamedPath);
296
+ } catch {
297
+ return renamedPath.slice(1, -1);
298
+ }
299
+ }
300
+ return renamedPath;
301
+ }
302
+ function isRuntimeCommitExcludedPath(path) {
303
+ const normalized = path.replace(/^\.\/+/, "").replace(/\/+$/, "");
304
+ return RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`));
305
+ }
306
+ function committableRunChangePaths(statusText) {
307
+ const seen = new Set;
308
+ const paths = [];
309
+ for (const line of statusText.split(/\r?\n/)) {
310
+ const path = statusPathFromShortLine(line);
311
+ if (!path || isRuntimeCommitExcludedPath(path) || seen.has(path))
312
+ continue;
313
+ seen.add(path);
314
+ paths.push(path);
315
+ }
316
+ return paths;
317
+ }
318
+ function hasCommittableRunChanges(statusText) {
319
+ return committableRunChangePaths(statusText).length > 0;
320
+ }
321
+ async function commitRunChanges(input) {
322
+ const status = await runChecked(input.command, ["status", "--short", "--untracked-files=all"], input.cwd, "git");
323
+ const statusText = status.stdout ?? "";
324
+ const committablePaths = committableRunChangePaths(statusText);
325
+ if (!statusText.trim() || committablePaths.length === 0) {
326
+ return { committed: false, status: statusText };
327
+ }
328
+ await runChecked(input.command, ["add", "-A", "--", ...committablePaths], input.cwd, "git");
329
+ const staged = await input.command(["diff", "--cached", "--quiet"], { cwd: input.cwd });
330
+ if (staged.exitCode === 0) {
331
+ return { committed: false, status: statusText };
332
+ }
333
+ if (staged.exitCode !== 1) {
334
+ throw new Error(`git diff --cached --quiet failed (${staged.exitCode}): ${staged.stderr ?? staged.stdout ?? ""}`.trim());
335
+ }
336
+ await runChecked(input.command, ["commit", "-m", input.message], input.cwd, "git");
337
+ return { committed: true, status: statusText };
338
+ }
339
+ async function closeIssueAfterMergedPr(input) {
340
+ await input.updateTaskSource(input.projectRoot, {
341
+ taskId: input.taskId,
342
+ sourceTask: input.sourceTask,
343
+ update: {
344
+ status: "closed",
345
+ comment: [
346
+ "<!-- rig:status-comment -->",
347
+ "### Rig status: closed",
348
+ "",
349
+ "Rig PR merged and closed this task.",
350
+ "",
351
+ `- Run: ${input.runId}`,
352
+ `- PR merged: ${input.prUrl}`
353
+ ].join(`
354
+ `)
355
+ }
356
+ });
357
+ }
358
+ function parseGitHubRepoFromPrUrl(prUrl) {
359
+ const match = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+\/?$/i.exec(prUrl.trim());
360
+ return match ? { owner: match[1], repo: match[2] } : null;
361
+ }
362
+ async function resolveRepoDefaultMergeFlag(input) {
363
+ const repo = parseGitHubRepoFromPrUrl(input.prUrl);
364
+ if (!repo)
365
+ throw new Error(`Cannot resolve GitHub repository from PR URL: ${input.prUrl}`);
366
+ const result = await input.command(["api", `repos/${repo.owner}/${repo.repo}`], input.cwd ? { cwd: input.cwd } : undefined);
367
+ if (result.exitCode !== 0) {
368
+ throw new Error(`Could not read repository merge policy for ${repo.owner}/${repo.repo}: ${result.stderr ?? result.stdout ?? "gh api failed"}`.trim());
369
+ }
370
+ try {
371
+ const parsed = JSON.parse(result.stdout ?? "{}");
372
+ if (parsed.allow_merge_commit !== false)
373
+ return "--merge";
374
+ if (parsed.allow_squash_merge !== false)
375
+ return "--squash";
376
+ if (parsed.allow_rebase_merge !== false)
377
+ return "--rebase";
378
+ } catch (error) {
379
+ throw new Error(`Could not parse repository merge policy for ${repo.owner}/${repo.repo}: ${error instanceof Error ? error.message : String(error)}`);
380
+ }
381
+ throw new Error(`Repository ${repo.owner}/${repo.repo} has no enabled merge method for repo-default merge.`);
382
+ }
383
+ async function runRepoDefaultMerge(input) {
384
+ const merge = input.config?.merge ?? {};
385
+ if (merge.mode === "off")
386
+ return;
387
+ const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
388
+ const matchHeadSha = strictMergeHeadShaFromGate(input.strictGate, input.prUrl, requireGreptile);
389
+ const method = merge.method ?? "repo-default";
390
+ const args = ["pr", "merge", input.prUrl];
391
+ if (method === "repo-default") {
392
+ args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, cwd: input.cwd }));
393
+ } else {
394
+ args.push(`--${method}`);
395
+ }
396
+ args.push("--match-head-commit", matchHeadSha);
397
+ if (merge.deleteBranch === true) {
398
+ args.push("--delete-branch");
399
+ }
400
+ if (merge.bypass === true) {
401
+ args.push("--admin");
402
+ }
403
+ await runChecked(input.command, args, input.cwd);
404
+ }
405
+ function shouldAttemptRigMerge(config) {
406
+ const mode = config?.merge?.mode;
407
+ return mode !== "off" && mode !== "pr-ready";
408
+ }
409
+ function isPendingOnlyGate(result) {
410
+ return result.pending && result.reasonDetails.length > 0 && result.reasonDetails.every((reason) => reason.reasonClass === "pending" && reason.suggestedAction === "wait");
411
+ }
412
+ function gateNeedsGreptileRereview(result) {
413
+ if (result.approved)
414
+ return false;
415
+ const staleShaEvidence = result.reasonDetails.some((reason) => reason.surface === "greptile" && (reason.code === "greptile_stale" || reason.code === "greptile_not_current_head" && !!reason.reviewedSha && reason.reviewedSha !== reason.headSha));
416
+ const hasParseableGreptileScore = !!result.evidence.greptile.score;
417
+ const greptileNeedsPrompt = !hasParseableGreptileScore && result.reasonDetails.some((reason) => reason.surface === "greptile" && reason.suggestedAction === "ask_greptile" && (reason.code === "greptile_not_current_head" || reason.code === "greptile_score_missing" || reason.code === "greptile_mapping_unproven"));
418
+ const greptileStillRunning = result.reasonDetails.some((reason) => reason.code === "greptile_pending" || reason.code === "greptile_missing");
419
+ return (staleShaEvidence || greptileNeedsPrompt) && !greptileStillRunning;
420
+ }
421
+ async function requestGreptileRereview(input) {
422
+ const sha = input.headSha?.trim() || "unknown";
423
+ const marker = `<!-- ${GREPTILE_REREVIEW_MARKER_PREFIX}:${sha} -->`;
424
+ const existing = await input.command(["pr", "view", input.prUrl, "--json", "comments"], input.cwd ? { cwd: input.cwd } : undefined);
425
+ if (existing.exitCode === 0 && (existing.stdout ?? "").includes(marker)) {
426
+ return false;
427
+ }
428
+ await runChecked(input.command, [
429
+ "pr",
430
+ "comment",
431
+ input.prUrl,
432
+ "--body",
433
+ `${marker}
434
+ @greptileai review
435
+
436
+ Rig strict merge gate: Greptile evidence is bound to an older commit. Requesting a fresh review of the current head (${sha}). Merge waits until Greptile re-reviews this exact commit.`
437
+ ], input.cwd);
438
+ return true;
439
+ }
440
+ async function pushBranchSyncedWithOrigin(input) {
441
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
442
+ const fetched = await input.gitCommand(["fetch", "origin", branch], { cwd: input.projectRoot });
443
+ if (fetched.exitCode === 0) {
444
+ const originRef = `origin/${branch}`;
445
+ const behind = await input.gitCommand(["rev-list", "--count", `HEAD..${originRef}`], { cwd: input.projectRoot });
446
+ const behindCount = Number.parseInt((behind.stdout ?? "").trim(), 10);
447
+ if (behind.exitCode === 0 && Number.isFinite(behindCount) && behindCount > 0) {
448
+ await runChecked(input.gitCommand, ["rebase", "--autostash", originRef], input.projectRoot, "git");
449
+ }
450
+ }
451
+ const pushed = await input.gitCommand(["push", "--set-upstream", "origin", branch], { cwd: input.projectRoot });
452
+ if (pushed.exitCode !== 0) {
453
+ await runChecked(input.gitCommand, ["push", "--set-upstream", "--force-with-lease", "origin", branch], input.projectRoot, "git");
454
+ }
455
+ }
456
+ async function syncBranchAfterPrFeedback(input) {
457
+ if (!input.gitCommand)
458
+ return;
459
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
460
+ await commitRunChanges({
461
+ cwd: input.projectRoot,
462
+ message: `rig: address PR feedback for task ${input.taskId}`,
463
+ command: input.gitCommand
464
+ });
465
+ await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
466
+ }
467
+ async function runPrAutomation(input) {
468
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
469
+ const prConfig = input.config?.pr ?? {};
470
+ const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
471
+ if (prConfig.mode === "off" || prConfig.mode === "ask") {
472
+ return { status: "skipped", iterations: 0, actionableFeedback: [] };
473
+ }
474
+ const body = buildPrAutomationBody({
475
+ taskId: input.taskId,
476
+ runId: input.runId,
477
+ summary: input.sourceTask?.title ? `Rig completed: ${input.sourceTask.title}` : null,
478
+ uploadedSnapshot: input.uploadedSnapshot
479
+ });
480
+ if (input.gitCommand) {
481
+ await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
482
+ }
483
+ const createArgs = [
484
+ "pr",
485
+ "create",
486
+ "--head",
487
+ branch,
488
+ "--title",
489
+ input.sourceTask?.title?.trim() || `Rig task ${input.taskId}`,
490
+ "--body",
491
+ body
492
+ ];
493
+ const createResult = await input.command(createArgs, { cwd: input.projectRoot });
494
+ const existingPrUrl = createResult.exitCode === 0 ? null : /pull request .*already exists/i.test(`${createResult.stderr ?? ""}
495
+ ${createResult.stdout ?? ""}`) ? findPrUrl(`${createResult.stderr ?? ""}
496
+ ${createResult.stdout ?? ""}`) : null;
497
+ if (createResult.exitCode !== 0 && !existingPrUrl) {
498
+ throw new Error(`gh ${createArgs.join(" ")} failed (${createResult.exitCode}): ${createResult.stderr ?? createResult.stdout ?? ""}`.trim());
499
+ }
500
+ const prUrl = existingPrUrl ?? normalizePrUrl(createResult.stdout);
501
+ if (existingPrUrl) {
502
+ await ensureExistingPrBodyHasRigMarkers({ prUrl, body, command: input.command, cwd: input.projectRoot });
503
+ }
504
+ await input.lifecycle?.onPrOpened?.({ prUrl });
505
+ const { maxPrFixIterations } = resolvePrAutomationLimits(input.config);
506
+ let latestFeedback = [];
507
+ let pendingElapsedMs = 0;
508
+ const shouldMerge = shouldAttemptRigMerge(input.config);
509
+ for (let iteration = 1;iteration <= maxPrFixIterations; iteration += 1) {
510
+ await input.lifecycle?.onReviewCiStarted?.({ prUrl, iteration });
511
+ if (!shouldMerge) {
512
+ const checks = prConfig.watchChecks === false ? [] : await readPrChecks({ prUrl, command: input.command, cwd: input.projectRoot });
513
+ const reviewThreads = prConfig.autoFixReview === false ? [] : parsePrViewReviewThreads((await runChecked(input.command, ["pr", "view", prUrl, "--json", "reviewDecision,reviews"], input.projectRoot)).stdout);
514
+ latestFeedback = collectActionablePrFeedback({
515
+ checks,
516
+ reviewThreads,
517
+ allowedFailures: input.config?.merge?.allowedFailures ?? []
518
+ });
519
+ const pendingChecks = collectPendingPrChecks({ checks, allowedFailures: input.config?.merge?.allowedFailures ?? [] });
520
+ if (latestFeedback.length === 0 && pendingChecks.length > 0) {
521
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
522
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
523
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
524
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: pendingChecks.map((name) => `Check still pending: ${name}`), merged: false };
525
+ }
526
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
527
+ await (input.sleep ?? Bun.sleep)(sleepMs);
528
+ pendingElapsedMs += sleepMs;
529
+ continue;
530
+ }
531
+ if (latestFeedback.length === 0) {
532
+ pendingElapsedMs = 0;
533
+ return { status: "opened", prUrl, iterations: iteration, actionableFeedback: [], merged: false };
534
+ }
535
+ pendingElapsedMs = 0;
536
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
537
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
538
+ }
539
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
540
+ await input.steerPi([
541
+ `PR automation found actionable feedback on ${prUrl}.`,
542
+ `Fix iteration ${iteration + 1}/${maxPrFixIterations}.`,
543
+ "",
544
+ ...latestFeedback.map((entry) => `- ${entry}`)
545
+ ].join(`
546
+ `));
547
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
548
+ continue;
549
+ }
550
+ const gate = await runStrictPrMergeGate({
551
+ projectRoot: input.projectRoot,
552
+ prUrl,
553
+ taskId: input.taskId,
554
+ runId: input.runId,
555
+ cycle: iteration,
556
+ command: input.command,
557
+ artifactRoot: input.artifactRoot,
558
+ allowedFailures: input.config?.merge?.allowedFailures ?? [],
559
+ greptileApi: requireGreptile ? input.greptileApi : undefined,
560
+ requireGreptile
561
+ });
562
+ latestFeedback = [...gate.actionableFeedback];
563
+ if (requireGreptile && gateNeedsGreptileRereview(gate)) {
564
+ const requested = await requestGreptileRereview({
565
+ prUrl,
566
+ headSha: gate.evidence.headSha ?? null,
567
+ command: input.command,
568
+ cwd: input.projectRoot
569
+ });
570
+ if (requested) {
571
+ await input.lifecycle?.onFeedback?.({
572
+ prUrl,
573
+ iteration,
574
+ feedback: [`Requested a fresh Greptile review for current head ${gate.evidence.headSha ?? "unknown"}; merge stays blocked until Greptile re-reviews it.`]
575
+ });
576
+ }
577
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
578
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
579
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
580
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
581
+ }
582
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
583
+ await (input.sleep ?? Bun.sleep)(sleepMs);
584
+ pendingElapsedMs += sleepMs;
585
+ continue;
586
+ }
587
+ if (gate.approved) {
588
+ pendingElapsedMs = 0;
589
+ const finalGate = await runStrictPrMergeGate({
590
+ projectRoot: input.projectRoot,
591
+ prUrl,
592
+ taskId: input.taskId,
593
+ runId: input.runId,
594
+ cycle: iteration,
595
+ command: input.command,
596
+ artifactRoot: input.artifactRoot,
597
+ allowedFailures: input.config?.merge?.allowedFailures ?? [],
598
+ greptileApi: requireGreptile ? input.greptileApi : undefined,
599
+ requireGreptile,
600
+ final: true
601
+ });
602
+ if (finalGate.approved) {
603
+ await input.lifecycle?.onMergeStarted?.({ prUrl });
604
+ await runRepoDefaultMerge({ prUrl, config: input.config, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
605
+ await input.lifecycle?.onMerged?.({ prUrl });
606
+ return { status: "merged", prUrl, iterations: iteration, actionableFeedback: [], merged: true };
607
+ }
608
+ latestFeedback = [...finalGate.actionableFeedback];
609
+ if (isPendingOnlyGate(finalGate)) {
610
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
611
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
612
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
613
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
614
+ }
615
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
616
+ await (input.sleep ?? Bun.sleep)(sleepMs);
617
+ pendingElapsedMs += sleepMs;
618
+ continue;
619
+ }
620
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
621
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
622
+ }
623
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
624
+ await input.steerPi(finalGate.steeringPrompt);
625
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
626
+ continue;
627
+ }
628
+ if (isPendingOnlyGate(gate)) {
629
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
630
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
631
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
632
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
633
+ }
634
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
635
+ await (input.sleep ?? Bun.sleep)(sleepMs);
636
+ pendingElapsedMs += sleepMs;
637
+ continue;
638
+ }
639
+ pendingElapsedMs = 0;
640
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
641
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
642
+ }
643
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
644
+ await input.steerPi(gate.steeringPrompt);
645
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
646
+ }
647
+ return { status: "needs_attention", prUrl, iterations: maxPrFixIterations, actionableFeedback: latestFeedback, merged: false };
648
+ }
649
+ var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->", RIG_RUNTIME_COMMIT_EXCLUDES, GREPTILE_REREVIEW_MARKER_PREFIX = "rig:greptile-rereview";
650
+ var init_pr_automation = __esm(() => {
651
+ RIG_RUNTIME_COMMIT_EXCLUDES = [
652
+ ".rig",
653
+ "artifacts",
654
+ "node_modules"
655
+ ];
656
+ });
3
657
 
4
658
  // packages/bundle-default-lifecycle/src/stages/types.ts
5
659
  function defineDefaultLifecycleStage(input) {
@@ -18,8 +672,8 @@ var commitStage = defineDefaultLifecycleStage({
18
672
  calls: ["commitRunChanges"]
19
673
  });
20
674
  async function runCommitStage(input) {
21
- const { commitRunChanges } = await import("@rig/runtime/control-plane/native/pr-automation");
22
- return await commitRunChanges(input);
675
+ const { commitRunChanges: commitRunChanges2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
676
+ return await commitRunChanges2(input);
23
677
  }
24
678
  export {
25
679
  runCommitStage,
@@ -18,8 +18,9 @@ var isolationStage = defineDefaultLifecycleStage({
18
18
  calls: ["ensureAgentRuntime"]
19
19
  });
20
20
  async function runIsolationStage(input) {
21
- const { ensureAgentRuntime } = await import("@rig/runtime/control-plane/runtime/isolation");
22
- return await ensureAgentRuntime(input);
21
+ const { requireIsolationBackend } = await import("@rig/runtime/control-plane/isolation-backend-port");
22
+ const backend = await requireIsolationBackend(input.projectRoot);
23
+ return await backend.ensureAgentRuntime(input);
23
24
  }
24
25
  export {
25
26
  runIsolationStage,