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

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 (54) hide show
  1. package/dist/src/cli.d.ts +1 -7
  2. package/dist/src/cli.js +5 -2
  3. package/dist/src/control-plane/completion-verification.js +1591 -118
  4. package/dist/src/control-plane/hooks/inject-context.d.ts +2 -0
  5. package/dist/src/control-plane/hooks/inject-context.js +175 -0
  6. package/dist/src/control-plane/hooks/shared.d.ts +11 -0
  7. package/dist/src/control-plane/hooks/shared.js +44 -0
  8. package/dist/src/control-plane/hooks/submodule-branch.d.ts +2 -0
  9. package/dist/src/control-plane/hooks/submodule-branch.js +432 -0
  10. package/dist/src/control-plane/hooks/task-runtime-start.d.ts +2 -0
  11. package/dist/src/control-plane/hooks/task-runtime-start.js +429 -0
  12. package/dist/src/control-plane/materialize-task-config.d.ts +29 -0
  13. package/dist/src/control-plane/materialize-task-config.js +95 -0
  14. package/dist/src/control-plane/native/git-ops.d.ts +67 -0
  15. package/dist/src/control-plane/native/git-ops.js +1390 -0
  16. package/dist/src/control-plane/policy.d.ts +3 -0
  17. package/dist/src/control-plane/policy.js +226 -0
  18. package/dist/src/control-plane/pr-automation.d.ts +2 -0
  19. package/dist/src/control-plane/pr-automation.js +26 -16
  20. package/dist/src/control-plane/pr-merge-gate-cap.d.ts +10 -0
  21. package/dist/src/control-plane/pr-merge-gate-cap.js +13 -0
  22. package/dist/src/control-plane/task-data.d.ts +13 -0
  23. package/dist/src/control-plane/task-data.js +12 -0
  24. package/dist/src/control-plane/task-verify.js +131 -59
  25. package/dist/src/control-plane/verifier.d.ts +1 -3
  26. package/dist/src/control-plane/verifier.js +133 -57
  27. package/dist/src/defaultPipeline.d.ts +1 -1
  28. package/dist/src/defaultPipeline.js +5 -2
  29. package/dist/src/index.d.ts +0 -2
  30. package/dist/src/index.js +1908 -290
  31. package/dist/src/native/closeout-runners.js +22 -2
  32. package/dist/src/native/github-auth-env.d.ts +2 -0
  33. package/dist/src/native/github-auth-env.js +25 -0
  34. package/dist/src/native/host-git.d.ts +6 -0
  35. package/dist/src/native/host-git.js +62 -0
  36. package/dist/src/native/in-process-closeout.d.ts +1 -3
  37. package/dist/src/native/in-process-closeout.js +0 -794
  38. package/dist/src/pipelineCloseout.js +1905 -185
  39. package/dist/src/plugin.js +2843 -145
  40. package/dist/src/stages/auto-merge.js +28 -16
  41. package/dist/src/stages/commit.js +28 -16
  42. package/dist/src/stages/isolation.d.ts +1 -1
  43. package/dist/src/stages/isolation.js +5 -3
  44. package/dist/src/stages/merge-gate.js +35 -3
  45. package/dist/src/stages/open-pr.js +28 -16
  46. package/dist/src/stages/push.js +28 -16
  47. package/dist/src/stages/source-closeout.js +28 -16
  48. package/package.json +29 -16
  49. package/dist/src/branch-naming.d.ts +0 -15
  50. package/dist/src/branch-naming.js +0 -33
  51. package/dist/src/closeoutEquivalence.d.ts +0 -37
  52. package/dist/src/closeoutEquivalence.js +0 -78
  53. package/dist/src/closeoutShadowHarness.d.ts +0 -27
  54. package/dist/src/closeoutShadowHarness.js +0 -29
@@ -14,32 +14,2289 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+ var __require = import.meta.require;
17
18
 
18
- // packages/bundle-default-lifecycle/src/control-plane/verifier.ts
19
- import { existsSync, mkdirSync, writeFileSync } from "fs";
20
- import { resolve } from "path";
21
- import { resolveRuntimeSecrets } from "@rig/runtime/control-plane/runtime/baked-secrets";
22
- import { readPrMetadata } from "@rig/runtime/control-plane/native/git-ops";
23
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
24
- import { readConfiguredTaskSourceTask } from "@rig/runtime/control-plane/tasks/source-lifecycle";
25
- import { artifactDirForId, lookupTask, readTaskConfig } from "@rig/runtime/control-plane/native/task-state";
26
- import { nowIso, resolveHarnessPaths, runCapture } from "@rig/runtime/control-plane/native/utils";
19
+ // packages/bundle-default-lifecycle/src/control-plane/pr-merge-gate-cap.ts
20
+ var exports_pr_merge_gate_cap = {};
21
+ __export(exports_pr_merge_gate_cap, {
22
+ resolvePrMergeGateService: () => resolvePrMergeGateService
23
+ });
24
+ import { PR_MERGE_GATE } from "@rig/contracts";
25
+ import { defineCapability } from "@rig/core/capability";
26
+ import { resolvePluginHost } from "@rig/core/project-plugins";
27
+ async function resolvePrMergeGateService(projectRoot) {
28
+ const { host } = await resolvePluginHost(projectRoot);
29
+ return PrMergeGateCap.require(host);
30
+ }
31
+ var PrMergeGateCap;
32
+ var init_pr_merge_gate_cap = __esm(() => {
33
+ PrMergeGateCap = defineCapability(PR_MERGE_GATE);
34
+ });
35
+
36
+ // packages/bundle-default-lifecycle/src/control-plane/pr-automation.ts
37
+ var exports_pr_automation = {};
38
+ __export(exports_pr_automation, {
39
+ runRepoDefaultMerge: () => runRepoDefaultMerge,
40
+ runPrAutomation: () => runPrAutomation,
41
+ resolvePrAutomationLimits: () => resolvePrAutomationLimits,
42
+ requestGreptileRereview: () => requestGreptileRereview,
43
+ pushBranchSyncedWithOrigin: () => pushBranchSyncedWithOrigin,
44
+ hasCommittableRunChanges: () => hasCommittableRunChanges,
45
+ gateNeedsGreptileRereview: () => gateNeedsGreptileRereview,
46
+ commitRunChanges: () => commitRunChanges,
47
+ collectPendingPrChecks: () => collectPendingPrChecks,
48
+ collectActionablePrFeedback: () => collectActionablePrFeedback,
49
+ closeIssueAfterMergedPr: () => closeIssueAfterMergedPr,
50
+ buildPrAutomationBody: () => buildPrAutomationBody,
51
+ UPLOADED_SNAPSHOT_PR_MARKER: () => UPLOADED_SNAPSHOT_PR_MARKER
52
+ });
53
+ import { assertSafeGitBranchName } from "@rig/core/safe-identifiers";
54
+ function positiveInt(value, fallback) {
55
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
56
+ }
57
+ function resolvePrAutomationLimits(config) {
58
+ return {
59
+ maxPrFixIterations: positiveInt(config?.automation?.maxPrFixIterations, 100500)
60
+ };
61
+ }
62
+ function buildPrAutomationBody(input) {
63
+ const lines = [
64
+ input.summary?.trim() || "Rig completed this task autonomously.",
65
+ "",
66
+ `Run: ${input.runId}`
67
+ ];
68
+ if (/^\d+$/.test(input.taskId)) {
69
+ lines.push("", `Closes #${input.taskId}`);
70
+ }
71
+ if (input.uploadedSnapshot) {
72
+ 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.");
73
+ }
74
+ return lines.join(`
75
+ `);
76
+ }
77
+ function wildcardToRegExp(pattern) {
78
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
79
+ return new RegExp(`^${escaped}$`, "i");
80
+ }
81
+ function isAllowedFailure(name, allowedFailures) {
82
+ return allowedFailures.some((pattern) => wildcardToRegExp(pattern).test(name));
83
+ }
84
+ function isPendingCheck(check) {
85
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
86
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
87
+ return ["pending", "queued", "in_progress", "waiting", "requested", "expected", "action_required"].includes(conclusion) || ["pending", "queued", "in_progress", "waiting", "requested", "expected"].includes(state);
88
+ }
89
+ function isPassingCheck(check) {
90
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
91
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
92
+ return ["success", "successful", "passed", "neutral", "skipped"].includes(conclusion) || ["success", "successful", "passed", "completed"].includes(state);
93
+ }
94
+ function isFailingCheck(check) {
95
+ const conclusion = String(check.conclusion ?? "").toLowerCase();
96
+ const state = String(check.state ?? check.status ?? "").toLowerCase();
97
+ return ["failure", "failed", "timed_out", "action_required", "cancelled", "error"].includes(conclusion) || ["failure", "failed", "timed_out", "action_required", "cancelled", "error"].includes(state);
98
+ }
99
+ function collectPendingPrChecks(input) {
100
+ const allowedFailures = input.allowedFailures ?? [];
101
+ const pending = [];
102
+ for (const check of input.checks ?? []) {
103
+ const name = check.name.trim();
104
+ if (!name || isAllowedFailure(name, allowedFailures))
105
+ continue;
106
+ if (isPendingCheck(check) && !isPassingCheck(check))
107
+ pending.push(name);
108
+ }
109
+ return pending;
110
+ }
111
+ function collectActionablePrFeedback(input) {
112
+ const allowedFailures = input.allowedFailures ?? [];
113
+ const feedback = [];
114
+ for (const check of input.checks ?? []) {
115
+ const name = check.name.trim();
116
+ if (!name || !isFailingCheck(check) || isAllowedFailure(name, allowedFailures))
117
+ continue;
118
+ feedback.push(`Check failed: ${name}${check.detailsUrl ? ` (${check.detailsUrl})` : ""}`);
119
+ }
120
+ for (const thread of input.reviewThreads ?? []) {
121
+ if (thread.resolved === true)
122
+ continue;
123
+ const body = thread.body.trim();
124
+ if (!body)
125
+ continue;
126
+ feedback.push(`Review feedback from ${thread.author?.trim() || "reviewer"}: ${body}`);
127
+ }
128
+ return feedback;
129
+ }
130
+ function parseJsonArray(value) {
131
+ if (!value?.trim())
132
+ return [];
133
+ try {
134
+ const parsed = JSON.parse(value);
135
+ return Array.isArray(parsed) ? parsed : [];
136
+ } catch {
137
+ return [];
138
+ }
139
+ }
140
+ function parsePrChecks(value) {
141
+ return parseJsonArray(value).flatMap((entry) => {
142
+ if (!entry || typeof entry !== "object")
143
+ return [];
144
+ const record = entry;
145
+ const name = typeof record.name === "string" ? record.name : "";
146
+ if (!name.trim())
147
+ return [];
148
+ return [{
149
+ name,
150
+ status: typeof record.status === "string" ? record.status : null,
151
+ state: typeof record.state === "string" ? record.state : null,
152
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
153
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
154
+ }];
155
+ });
156
+ }
157
+ function parsePrViewStatusCheckRollup(value) {
158
+ if (!value?.trim())
159
+ return [];
160
+ try {
161
+ const parsed = JSON.parse(value);
162
+ const rollup = Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : [];
163
+ return rollup.flatMap((entry) => {
164
+ if (!entry || typeof entry !== "object")
165
+ return [];
166
+ const record = entry;
167
+ const name = typeof record.name === "string" ? record.name : "";
168
+ if (!name.trim())
169
+ return [];
170
+ return [{
171
+ name,
172
+ status: typeof record.status === "string" ? record.status : null,
173
+ state: typeof record.state === "string" ? record.state : null,
174
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
175
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
176
+ }];
177
+ });
178
+ } catch {
179
+ return [];
180
+ }
181
+ }
182
+ async function readPrChecks(input) {
183
+ const checks = await input.command(["pr", "checks", input.prUrl, "--json", "name,state,link"], input.cwd ? { cwd: input.cwd } : undefined);
184
+ if (checks.exitCode === 0) {
185
+ return parsePrChecks(checks.stdout);
186
+ }
187
+ const combined = `${checks.stderr ?? ""}
188
+ ${checks.stdout ?? ""}`;
189
+ if (!/unknown flag.*--json|unknown flag: --json|unknown shorthand flag/i.test(combined)) {
190
+ throw new Error(`gh pr checks ${input.prUrl} --json name,state,link failed (${checks.exitCode}): ${checks.stderr ?? checks.stdout ?? ""}`.trim());
191
+ }
192
+ const view = await runChecked(input.command, ["pr", "view", input.prUrl, "--json", "statusCheckRollup"], input.cwd, "gh");
193
+ return parsePrViewStatusCheckRollup(view.stdout);
194
+ }
195
+ function parsePrViewReviewThreads(value) {
196
+ if (!value?.trim())
197
+ return [];
198
+ try {
199
+ const parsed = JSON.parse(value);
200
+ const feedback = [];
201
+ const reviewThreads = parsed.reviewThreads;
202
+ if (Array.isArray(reviewThreads)) {
203
+ feedback.push(...reviewThreads.flatMap((entry) => {
204
+ if (!entry || typeof entry !== "object")
205
+ return [];
206
+ const record = entry;
207
+ const body = typeof record.body === "string" ? record.body : null;
208
+ if (!body)
209
+ return [];
210
+ return [{
211
+ id: typeof record.id === "string" ? record.id : null,
212
+ body,
213
+ resolved: typeof record.resolved === "boolean" ? record.resolved : false,
214
+ author: typeof record.author === "string" ? record.author : null
215
+ }];
216
+ }));
217
+ }
218
+ const reviews = parsed.reviews;
219
+ if (Array.isArray(reviews)) {
220
+ feedback.push(...reviews.flatMap((entry) => {
221
+ if (!entry || typeof entry !== "object")
222
+ return [];
223
+ const record = entry;
224
+ const state = typeof record.state === "string" ? record.state.toUpperCase() : "";
225
+ if (state !== "CHANGES_REQUESTED")
226
+ return [];
227
+ const body = typeof record.body === "string" && record.body.trim() ? record.body : "Changes requested by reviewer.";
228
+ const author = record.author && typeof record.author === "object" ? record.author.login : null;
229
+ return [{
230
+ id: typeof record.id === "string" ? record.id : null,
231
+ body,
232
+ resolved: false,
233
+ author: typeof author === "string" ? author : null
234
+ }];
235
+ }));
236
+ }
237
+ const reviewDecision = typeof parsed.reviewDecision === "string" ? parsed.reviewDecision.toUpperCase() : "";
238
+ if (reviewDecision === "CHANGES_REQUESTED" && feedback.length === 0) {
239
+ feedback.push({ body: "Changes requested by reviewer.", resolved: false });
240
+ }
241
+ return feedback;
242
+ } catch {
243
+ return [];
244
+ }
245
+ }
246
+ function findPrUrl(output) {
247
+ return output?.split(/\s+/).map((part) => part.trim()).find((part) => /^https?:\/\/\S+\/pull\/\d+$/i.test(part)) ?? null;
248
+ }
249
+ function normalizePrUrl(stdout) {
250
+ const url = findPrUrl(stdout);
251
+ if (!url)
252
+ throw new Error("gh pr create did not return a PR URL");
253
+ return url;
254
+ }
255
+ function parseGitHubPullRequestUrl(prUrl) {
256
+ try {
257
+ const parsed = new URL(prUrl);
258
+ const [owner, repo, kind, number] = parsed.pathname.split("/").filter(Boolean);
259
+ if (!owner || !repo || kind !== "pull" || !number || !/^\d+$/.test(number))
260
+ return null;
261
+ return { owner, repo, number };
262
+ } catch {
263
+ return null;
264
+ }
265
+ }
266
+ async function ensureExistingPrBodyHasRigMarkers(input) {
267
+ const view = await input.command(["pr", "view", input.prUrl, "--json", "body"], input.cwd ? { cwd: input.cwd } : undefined);
268
+ if (view.exitCode !== 0) {
269
+ throw new Error(`gh pr view ${input.prUrl} --json body failed (${view.exitCode}): ${view.stderr ?? view.stdout ?? ""}`.trim());
270
+ }
271
+ let currentBody = "";
272
+ try {
273
+ const parsed = JSON.parse(view.stdout ?? "{}");
274
+ currentBody = typeof parsed.body === "string" ? parsed.body : "";
275
+ } catch {
276
+ currentBody = "";
277
+ }
278
+ const requiredBlocks = input.body.split(/\n{2,}/).map((block) => block.trim()).filter((block) => /^Run: /i.test(block) || /^Closes #\d+/i.test(block));
279
+ const missing = requiredBlocks.filter((block) => {
280
+ if (/^Run: /i.test(block))
281
+ return !/^Run: \S+/im.test(currentBody);
282
+ return !currentBody.includes(block);
283
+ });
284
+ if (missing.length === 0)
285
+ return;
286
+ const nextBody = [currentBody.trim(), ...missing].filter(Boolean).join(`
287
+
288
+ `);
289
+ const pr = parseGitHubPullRequestUrl(input.prUrl);
290
+ if (!pr)
291
+ throw new Error(`Cannot update existing PR body for unrecognized PR URL: ${input.prUrl}`);
292
+ 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);
293
+ if (edit.exitCode !== 0) {
294
+ 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());
295
+ }
296
+ }
297
+ async function runChecked(command, args, cwd, label = "gh") {
298
+ const result = await command(args, cwd ? { cwd } : undefined);
299
+ if (result.exitCode !== 0) {
300
+ throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
301
+ }
302
+ return result;
303
+ }
304
+ function statusPathFromShortLine(line) {
305
+ const rawPath = line.length > 3 ? line.slice(3).trim() : "";
306
+ const renamedPath = rawPath.includes(" -> ") ? rawPath.split(" -> ").at(-1).trim() : rawPath;
307
+ if (renamedPath.startsWith('"') && renamedPath.endsWith('"')) {
308
+ try {
309
+ return JSON.parse(renamedPath);
310
+ } catch {
311
+ return renamedPath.slice(1, -1);
312
+ }
313
+ }
314
+ return renamedPath;
315
+ }
316
+ function isRuntimeCommitExcludedPath(path) {
317
+ const normalized = path.replace(/^\.\/+/, "").replace(/\/+$/, "");
318
+ return RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`));
319
+ }
320
+ function committableRunChangePaths(statusText) {
321
+ const seen = new Set;
322
+ const paths = [];
323
+ for (const line of statusText.split(/\r?\n/)) {
324
+ const path = statusPathFromShortLine(line);
325
+ if (!path || isRuntimeCommitExcludedPath(path) || seen.has(path))
326
+ continue;
327
+ seen.add(path);
328
+ paths.push(path);
329
+ }
330
+ return paths;
331
+ }
332
+ function hasCommittableRunChanges(statusText) {
333
+ return committableRunChangePaths(statusText).length > 0;
334
+ }
335
+ async function commitRunChanges(input) {
336
+ const status = await runChecked(input.command, ["status", "--short", "--untracked-files=all"], input.cwd, "git");
337
+ const statusText = status.stdout ?? "";
338
+ const committablePaths = committableRunChangePaths(statusText);
339
+ if (!statusText.trim() || committablePaths.length === 0) {
340
+ return { committed: false, status: statusText };
341
+ }
342
+ await runChecked(input.command, ["add", "-A", "--", ...committablePaths], input.cwd, "git");
343
+ const staged = await input.command(["diff", "--cached", "--quiet"], { cwd: input.cwd });
344
+ if (staged.exitCode === 0) {
345
+ return { committed: false, status: statusText };
346
+ }
347
+ if (staged.exitCode !== 1) {
348
+ throw new Error(`git diff --cached --quiet failed (${staged.exitCode}): ${staged.stderr ?? staged.stdout ?? ""}`.trim());
349
+ }
350
+ await runChecked(input.command, ["commit", "-m", input.message], input.cwd, "git");
351
+ return { committed: true, status: statusText };
352
+ }
353
+ async function closeIssueAfterMergedPr(input) {
354
+ await input.updateTaskSource(input.projectRoot, {
355
+ taskId: input.taskId,
356
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
357
+ update: {
358
+ status: "closed",
359
+ comment: [
360
+ "<!-- rig:status-comment -->",
361
+ "### Rig status: closed",
362
+ "",
363
+ "Rig PR merged and closed this task.",
364
+ "",
365
+ `- Run: ${input.runId}`,
366
+ `- PR merged: ${input.prUrl}`
367
+ ].join(`
368
+ `)
369
+ }
370
+ });
371
+ }
372
+ function parseGitHubRepoFromPrUrl(prUrl) {
373
+ const match = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+\/?$/i.exec(prUrl.trim());
374
+ return match ? { owner: match[1], repo: match[2] } : null;
375
+ }
376
+ async function resolveRepoDefaultMergeFlag(input) {
377
+ const repo = parseGitHubRepoFromPrUrl(input.prUrl);
378
+ if (!repo)
379
+ throw new Error(`Cannot resolve GitHub repository from PR URL: ${input.prUrl}`);
380
+ const result = await input.command(["api", `repos/${repo.owner}/${repo.repo}`], input.cwd ? { cwd: input.cwd } : undefined);
381
+ if (result.exitCode !== 0) {
382
+ throw new Error(`Could not read repository merge policy for ${repo.owner}/${repo.repo}: ${result.stderr ?? result.stdout ?? "gh api failed"}`.trim());
383
+ }
384
+ try {
385
+ const parsed = JSON.parse(result.stdout ?? "{}");
386
+ if (parsed.allow_merge_commit !== false)
387
+ return "--merge";
388
+ if (parsed.allow_squash_merge !== false)
389
+ return "--squash";
390
+ if (parsed.allow_rebase_merge !== false)
391
+ return "--rebase";
392
+ } catch (error) {
393
+ throw new Error(`Could not parse repository merge policy for ${repo.owner}/${repo.repo}: ${error instanceof Error ? error.message : String(error)}`);
394
+ }
395
+ throw new Error(`Repository ${repo.owner}/${repo.repo} has no enabled merge method for repo-default merge.`);
396
+ }
397
+ async function runRepoDefaultMerge(input) {
398
+ const merge = input.config?.merge ?? {};
399
+ if (merge.mode === "off")
400
+ return;
401
+ const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
402
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot ?? input.cwd ?? process.cwd());
403
+ const matchHeadSha = mergeGate.resolveHeadSha({ result: input.strictGate, prUrl: input.prUrl, requireGreptile });
404
+ const method = merge.method ?? "repo-default";
405
+ const args = ["pr", "merge", input.prUrl];
406
+ if (method === "repo-default") {
407
+ args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, ...input.cwd !== undefined ? { cwd: input.cwd } : {} }));
408
+ } else {
409
+ args.push(`--${method}`);
410
+ }
411
+ args.push("--match-head-commit", matchHeadSha);
412
+ if (merge.deleteBranch === true) {
413
+ args.push("--delete-branch");
414
+ }
415
+ if (merge.bypass === true) {
416
+ args.push("--admin");
417
+ }
418
+ await runChecked(input.command, args, input.cwd);
419
+ }
420
+ function shouldAttemptRigMerge(config) {
421
+ const mode = config?.merge?.mode;
422
+ return mode !== "off" && mode !== "pr-ready";
423
+ }
424
+ function isPendingOnlyGate(result) {
425
+ return result.pending && result.reasonDetails.length > 0 && result.reasonDetails.every((reason) => reason.reasonClass === "pending" && reason.suggestedAction === "wait");
426
+ }
427
+ function gateNeedsGreptileRereview(result) {
428
+ if (result.approved)
429
+ return false;
430
+ 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));
431
+ const hasParseableGreptileScore = !!result.evidence.greptile.score;
432
+ 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"));
433
+ const greptileStillRunning = result.reasonDetails.some((reason) => reason.code === "greptile_pending" || reason.code === "greptile_missing");
434
+ return (staleShaEvidence || greptileNeedsPrompt) && !greptileStillRunning;
435
+ }
436
+ async function requestGreptileRereview(input) {
437
+ const sha = input.headSha?.trim() || "unknown";
438
+ const marker = `<!-- ${GREPTILE_REREVIEW_MARKER_PREFIX}:${sha} -->`;
439
+ const existing = await input.command(["pr", "view", input.prUrl, "--json", "comments"], input.cwd ? { cwd: input.cwd } : undefined);
440
+ if (existing.exitCode === 0 && (existing.stdout ?? "").includes(marker)) {
441
+ return false;
442
+ }
443
+ await runChecked(input.command, [
444
+ "pr",
445
+ "comment",
446
+ input.prUrl,
447
+ "--body",
448
+ `${marker}
449
+ @greptileai review
450
+
451
+ 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.`
452
+ ], input.cwd);
453
+ return true;
454
+ }
455
+ async function pushBranchSyncedWithOrigin(input) {
456
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
457
+ const fetched = await input.gitCommand(["fetch", "origin", branch], { cwd: input.projectRoot });
458
+ if (fetched.exitCode === 0) {
459
+ const originRef = `origin/${branch}`;
460
+ const behind = await input.gitCommand(["rev-list", "--count", `HEAD..${originRef}`], { cwd: input.projectRoot });
461
+ const behindCount = Number.parseInt((behind.stdout ?? "").trim(), 10);
462
+ if (behind.exitCode === 0 && Number.isFinite(behindCount) && behindCount > 0) {
463
+ await runChecked(input.gitCommand, ["rebase", "--autostash", originRef], input.projectRoot, "git");
464
+ }
465
+ }
466
+ const pushed = await input.gitCommand(["push", "--set-upstream", "origin", branch], { cwd: input.projectRoot });
467
+ if (pushed.exitCode !== 0) {
468
+ await runChecked(input.gitCommand, ["push", "--set-upstream", "--force-with-lease", "origin", branch], input.projectRoot, "git");
469
+ }
470
+ }
471
+ async function syncBranchAfterPrFeedback(input) {
472
+ if (!input.gitCommand)
473
+ return;
474
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
475
+ await commitRunChanges({
476
+ cwd: input.projectRoot,
477
+ message: `rig: address PR feedback for task ${input.taskId}`,
478
+ command: input.gitCommand
479
+ });
480
+ await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
481
+ }
482
+ async function runPrAutomation(input) {
483
+ const branch = assertSafeGitBranchName(input.branch, "PR branch");
484
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot);
485
+ const prConfig = input.config?.pr ?? {};
486
+ const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
487
+ if (prConfig.mode === "off" || prConfig.mode === "ask") {
488
+ return { status: "skipped", iterations: 0, actionableFeedback: [] };
489
+ }
490
+ const body = buildPrAutomationBody({
491
+ taskId: input.taskId,
492
+ runId: input.runId,
493
+ summary: input.sourceTask?.title ? `Rig completed: ${input.sourceTask.title}` : null,
494
+ ...input.uploadedSnapshot !== undefined ? { uploadedSnapshot: input.uploadedSnapshot } : {}
495
+ });
496
+ if (input.gitCommand) {
497
+ await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
498
+ }
499
+ const createArgs = [
500
+ "pr",
501
+ "create",
502
+ "--head",
503
+ branch,
504
+ "--title",
505
+ input.sourceTask?.title?.trim() || `Rig task ${input.taskId}`,
506
+ "--body",
507
+ body
508
+ ];
509
+ const createResult = await input.command(createArgs, { cwd: input.projectRoot });
510
+ const existingPrUrl = createResult.exitCode === 0 ? null : /pull request .*already exists/i.test(`${createResult.stderr ?? ""}
511
+ ${createResult.stdout ?? ""}`) ? findPrUrl(`${createResult.stderr ?? ""}
512
+ ${createResult.stdout ?? ""}`) : null;
513
+ if (createResult.exitCode !== 0 && !existingPrUrl) {
514
+ throw new Error(`gh ${createArgs.join(" ")} failed (${createResult.exitCode}): ${createResult.stderr ?? createResult.stdout ?? ""}`.trim());
515
+ }
516
+ const prUrl = existingPrUrl ?? normalizePrUrl(createResult.stdout);
517
+ if (existingPrUrl) {
518
+ await ensureExistingPrBodyHasRigMarkers({ prUrl, body, command: input.command, cwd: input.projectRoot });
519
+ }
520
+ await input.lifecycle?.onPrOpened?.({ prUrl });
521
+ const { maxPrFixIterations } = resolvePrAutomationLimits(input.config);
522
+ let latestFeedback = [];
523
+ let pendingElapsedMs = 0;
524
+ const shouldMerge = shouldAttemptRigMerge(input.config);
525
+ for (let iteration = 1;iteration <= maxPrFixIterations; iteration += 1) {
526
+ await input.lifecycle?.onReviewCiStarted?.({ prUrl, iteration });
527
+ if (!shouldMerge) {
528
+ const checks = prConfig.watchChecks === false ? [] : await readPrChecks({ prUrl, command: input.command, cwd: input.projectRoot });
529
+ const reviewThreads = prConfig.autoFixReview === false ? [] : parsePrViewReviewThreads((await runChecked(input.command, ["pr", "view", prUrl, "--json", "reviewDecision,reviews"], input.projectRoot)).stdout);
530
+ latestFeedback = collectActionablePrFeedback({
531
+ checks,
532
+ reviewThreads,
533
+ allowedFailures: input.config?.merge?.allowedFailures ?? []
534
+ });
535
+ const pendingChecks = collectPendingPrChecks({ checks, allowedFailures: input.config?.merge?.allowedFailures ?? [] });
536
+ if (latestFeedback.length === 0 && pendingChecks.length > 0) {
537
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
538
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
539
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
540
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: pendingChecks.map((name) => `Check still pending: ${name}`), merged: false };
541
+ }
542
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
543
+ await (input.sleep ?? Bun.sleep)(sleepMs);
544
+ pendingElapsedMs += sleepMs;
545
+ continue;
546
+ }
547
+ if (latestFeedback.length === 0) {
548
+ pendingElapsedMs = 0;
549
+ return { status: "opened", prUrl, iterations: iteration, actionableFeedback: [], merged: false };
550
+ }
551
+ pendingElapsedMs = 0;
552
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
553
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
554
+ }
555
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
556
+ await input.steerPi([
557
+ `PR automation found actionable feedback on ${prUrl}.`,
558
+ `Fix iteration ${iteration + 1}/${maxPrFixIterations}.`,
559
+ "",
560
+ ...latestFeedback.map((entry) => `- ${entry}`)
561
+ ].join(`
562
+ `));
563
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
564
+ continue;
565
+ }
566
+ const gate = await mergeGate.runGate({
567
+ projectRoot: input.projectRoot,
568
+ prUrl,
569
+ taskId: input.taskId,
570
+ runId: input.runId,
571
+ cycle: iteration,
572
+ command: input.command,
573
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
574
+ allowedFailures: input.config?.merge?.allowedFailures ?? [],
575
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
576
+ requireGreptile
577
+ });
578
+ latestFeedback = [...gate.actionableFeedback];
579
+ if (requireGreptile && gateNeedsGreptileRereview(gate)) {
580
+ const requested = await requestGreptileRereview({
581
+ prUrl,
582
+ headSha: gate.evidence.headSha ?? null,
583
+ command: input.command,
584
+ cwd: input.projectRoot
585
+ });
586
+ if (requested) {
587
+ await input.lifecycle?.onFeedback?.({
588
+ prUrl,
589
+ iteration,
590
+ feedback: [`Requested a fresh Greptile review for current head ${gate.evidence.headSha ?? "unknown"}; merge stays blocked until Greptile re-reviews it.`]
591
+ });
592
+ }
593
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
594
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
595
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
596
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
597
+ }
598
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
599
+ await (input.sleep ?? Bun.sleep)(sleepMs);
600
+ pendingElapsedMs += sleepMs;
601
+ continue;
602
+ }
603
+ if (gate.approved) {
604
+ pendingElapsedMs = 0;
605
+ const finalGate = await mergeGate.runGate({
606
+ projectRoot: input.projectRoot,
607
+ prUrl,
608
+ taskId: input.taskId,
609
+ runId: input.runId,
610
+ cycle: iteration,
611
+ command: input.command,
612
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
613
+ allowedFailures: input.config?.merge?.allowedFailures ?? [],
614
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
615
+ requireGreptile,
616
+ final: true
617
+ });
618
+ if (finalGate.approved) {
619
+ await input.lifecycle?.onMergeStarted?.({ prUrl });
620
+ await runRepoDefaultMerge({ prUrl, ...input.config !== undefined ? { config: input.config } : {}, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
621
+ await input.lifecycle?.onMerged?.({ prUrl });
622
+ return { status: "merged", prUrl, iterations: iteration, actionableFeedback: [], merged: true };
623
+ }
624
+ latestFeedback = [...finalGate.actionableFeedback];
625
+ if (isPendingOnlyGate(finalGate)) {
626
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
627
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
628
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
629
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
630
+ }
631
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
632
+ await (input.sleep ?? Bun.sleep)(sleepMs);
633
+ pendingElapsedMs += sleepMs;
634
+ continue;
635
+ }
636
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
637
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
638
+ }
639
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
640
+ await input.steerPi(finalGate.steeringPrompt);
641
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
642
+ continue;
643
+ }
644
+ if (isPendingOnlyGate(gate)) {
645
+ const timeoutMs = positiveInt(prConfig.pendingTimeoutMs, 600000);
646
+ const pollMs = positiveInt(prConfig.pendingPollMs, 15000);
647
+ if (iteration >= maxPrFixIterations || timeoutMs <= 0 || pendingElapsedMs >= timeoutMs) {
648
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
649
+ }
650
+ const sleepMs = Math.min(pollMs, timeoutMs - pendingElapsedMs);
651
+ await (input.sleep ?? Bun.sleep)(sleepMs);
652
+ pendingElapsedMs += sleepMs;
653
+ continue;
654
+ }
655
+ pendingElapsedMs = 0;
656
+ if (iteration >= maxPrFixIterations || prConfig.autoFixChecks === false && prConfig.autoFixReview === false) {
657
+ return { status: "needs_attention", prUrl, iterations: iteration, actionableFeedback: latestFeedback, merged: false };
658
+ }
659
+ await input.lifecycle?.onFeedback?.({ prUrl, iteration, feedback: latestFeedback });
660
+ await input.steerPi(gate.steeringPrompt);
661
+ await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
662
+ }
663
+ return { status: "needs_attention", prUrl, iterations: maxPrFixIterations, actionableFeedback: latestFeedback, merged: false };
664
+ }
665
+ var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->", RIG_RUNTIME_COMMIT_EXCLUDES, GREPTILE_REREVIEW_MARKER_PREFIX = "rig:greptile-rereview";
666
+ var init_pr_automation = __esm(() => {
667
+ init_pr_merge_gate_cap();
668
+ RIG_RUNTIME_COMMIT_EXCLUDES = [
669
+ ".rig",
670
+ "artifacts",
671
+ "node_modules"
672
+ ];
673
+ });
674
+
675
+ // packages/bundle-default-lifecycle/src/control-plane/task-data.ts
676
+ import { TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
677
+ import { defineCapability as defineCapability3 } from "@rig/core/capability";
678
+ import { requireInstalledCapability } from "@rig/core/capability-loaders";
679
+ function taskData() {
680
+ return requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before running the lifecycle.");
681
+ }
682
+ var TaskDataCap;
683
+ var init_task_data = __esm(() => {
684
+ TaskDataCap = defineCapability3(TASK_DATA_SERVICE_CAPABILITY);
685
+ });
686
+
687
+ // packages/bundle-default-lifecycle/src/native/github-auth-env.ts
688
+ import { existsSync, readFileSync } from "fs";
689
+ function cleanToken(value) {
690
+ const trimmed = value?.trim() ?? "";
691
+ return trimmed.length > 0 ? trimmed : null;
692
+ }
693
+ function authStateToken(env = process.env) {
694
+ const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
695
+ if (!file || !existsSync(file))
696
+ return null;
697
+ try {
698
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
699
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
700
+ } catch {
701
+ return null;
702
+ }
703
+ }
704
+ function resolveGitHubAuthToken(env = process.env) {
705
+ return cleanToken(env.RIG_GITHUB_TOKEN) ?? cleanToken(env.GH_TOKEN) ?? cleanToken(env.GITHUB_TOKEN) ?? authStateToken(env);
706
+ }
707
+ var init_github_auth_env = () => {};
708
+
709
+ // packages/bundle-default-lifecycle/src/native/host-git.ts
710
+ import { existsSync as existsSync2 } from "fs";
711
+ import { resolve as resolve2 } from "path";
712
+ function isRuntimeGatewayGitPath(candidate) {
713
+ return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
714
+ }
715
+ function isRuntimeGatewayGhPath(candidate) {
716
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
717
+ }
718
+ function resolveHostGitBinary() {
719
+ const candidates = [
720
+ process.env.RIG_GIT_BIN?.trim() || "",
721
+ "/usr/bin/git",
722
+ "/opt/homebrew/bin/git",
723
+ "/usr/local/bin/git"
724
+ ];
725
+ const bunResolved = Bun.which("git");
726
+ if (bunResolved && !isRuntimeGatewayGitPath(bunResolved)) {
727
+ candidates.push(bunResolved);
728
+ }
729
+ for (const candidate of candidates) {
730
+ if (!candidate || isRuntimeGatewayGitPath(candidate)) {
731
+ continue;
732
+ }
733
+ if (existsSync2(candidate)) {
734
+ return candidate;
735
+ }
736
+ }
737
+ return "git";
738
+ }
739
+ function resolveGithubCliBinary(options = {}) {
740
+ const candidates = new Set;
741
+ const explicit = process.env.RIG_GH_BIN?.trim();
742
+ if (explicit) {
743
+ candidates.add(explicit);
744
+ }
745
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
746
+ candidates.add(candidate);
747
+ }
748
+ if (options.scanPath) {
749
+ for (const entry of (process.env.PATH || "").split(":").map((e) => e.trim()).filter(Boolean)) {
750
+ candidates.add(resolve2(entry, "gh"));
751
+ }
752
+ }
753
+ const bunResolved = Bun.which("gh");
754
+ if (bunResolved) {
755
+ candidates.add(bunResolved);
756
+ }
757
+ for (const candidate of candidates) {
758
+ if (candidate && existsSync2(candidate) && !isRuntimeGatewayGhPath(candidate)) {
759
+ return candidate;
760
+ }
761
+ }
762
+ return "";
763
+ }
764
+ var init_host_git = () => {};
765
+
766
+ // packages/bundle-default-lifecycle/src/control-plane/native/git-ops.ts
767
+ import { existsSync as existsSync3, lstatSync, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
768
+ import { tmpdir } from "os";
769
+ import { dirname, isAbsolute, resolve as resolve3 } from "path";
770
+ import { fileURLToPath } from "url";
771
+ import { loadDotEnvSecrets, resolveRuntimeSecrets } from "@rig/core/baked-secrets";
772
+ import { loadRuntimeContext, loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
773
+ import { nowIso, runCapture as baseRunCapture } from "@rig/core/exec";
774
+ import { resolveCheckoutRoot as resolveMonorepoRoot } from "@rig/core/checkout-root";
775
+ import { getScopeRules } from "@rig/core/scope-rules";
776
+ import { safePathSegment } from "@rig/core/safe-identifiers";
777
+ function resolveOptionalMonorepoRoot(projectRoot) {
778
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
779
+ if (runtimeWorkspace && existsSync3(resolve3(runtimeWorkspace, ".git"))) {
780
+ return resolve3(runtimeWorkspace);
781
+ }
782
+ try {
783
+ return resolveMonorepoRoot(projectRoot);
784
+ } catch {
785
+ return null;
786
+ }
787
+ }
788
+ function escapeRegExp(value) {
789
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
790
+ }
791
+ function safeCurrentTaskId(projectRoot) {
792
+ try {
793
+ const taskId = taskData().currentTaskId(projectRoot);
794
+ return /^bd-[a-z0-9-]+$/.test(taskId) ? taskId : "";
795
+ } catch {
796
+ return "";
797
+ }
798
+ }
799
+ function gitCmd(projectRoot, repoRoot, ...args) {
800
+ return [resolveHostGitBinary(), "-C", repoRoot, ...args];
801
+ }
802
+ function shouldScopeGitCommit(args, hasTaskContext) {
803
+ if (!hasTaskContext) {
804
+ return false;
805
+ }
806
+ return args.includes("--scoped");
807
+ }
808
+ function gitStatus(projectRoot, taskId) {
809
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
810
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
811
+ const expected = resolvedTask ? `rig/${resolveTaskBranchId(projectRoot, resolvedTask)}` : "";
812
+ console.log("=== Git Flow Status ===");
813
+ if (resolvedTask) {
814
+ console.log(`Task: ${resolvedTask}`);
815
+ console.log(`Expected monorepo branch: ${expected}`);
816
+ } else {
817
+ console.log("Task: (none active)");
818
+ }
819
+ console.log("");
820
+ printRepoStatus(projectRoot, "project-rig", projectRoot, "");
821
+ const monorepoPath = monorepoRoot || resolveMonorepoRoot(projectRoot);
822
+ if (monorepoPath !== projectRoot) {
823
+ printRepoStatus(projectRoot, "monorepo", monorepoPath, expected);
824
+ }
825
+ }
826
+ function gitChanged(projectRoot, taskId, scoped) {
827
+ if (scoped) {
828
+ const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
829
+ if (!resolvedTask) {
830
+ throw new Error("No task specified and no active task in session. Use --task or omit --scoped.");
831
+ }
832
+ return taskData().changedFilesForTask(projectRoot, resolvedTask, true);
833
+ }
834
+ return taskData().changedFilesForTask(projectRoot, taskId || taskData().currentTaskId(projectRoot) || "", false);
835
+ }
836
+ function gitPreflight(projectRoot, taskId, strict) {
837
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
838
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
839
+ const expected = resolvedTask ? `rig/${resolveTaskBranchId(projectRoot, resolvedTask)}` : "";
840
+ console.log("=== Git Flow Preflight ===");
841
+ let issues = 0;
842
+ if (!existsSync3(resolve3(projectRoot, ".git"))) {
843
+ console.log(`ERROR: project root is not a git repo (${projectRoot})`);
844
+ issues += 1;
845
+ }
846
+ if (monorepoRoot && existsSync3(resolve3(monorepoRoot, ".git"))) {
847
+ const monoBranch = branchName(projectRoot, monorepoRoot);
848
+ if (expected && monoBranch !== expected) {
849
+ console.log(`WARN: monorepo branch is ${monoBranch}, expected ${expected} for task ${resolvedTask}`);
850
+ if (strict) {
851
+ issues += 1;
852
+ }
853
+ }
854
+ const monoChanges = changeCount(projectRoot, monorepoRoot);
855
+ if (monoChanges > 0 && !monoBranch.startsWith("rig/")) {
856
+ console.log(`WARN: monorepo has uncommitted changes on non-rig branch (${monoBranch})`);
857
+ issues += 1;
858
+ }
859
+ } else {
860
+ console.log(`WARN: monorepo repo unavailable.`);
861
+ }
862
+ const projectChanges = changeCount(projectRoot, projectRoot);
863
+ if (projectChanges > 0) {
864
+ console.log(`INFO: project-rig has ${projectChanges} changed file(s).`);
865
+ }
866
+ if (issues > 0) {
867
+ console.log(`Preflight: ${issues} issue(s) detected.`);
868
+ return false;
869
+ }
870
+ console.log("Preflight: OK");
871
+ return true;
872
+ }
873
+ function gitSyncBranch(projectRoot, taskId, targetRepo = "monorepo") {
874
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
875
+ if (!resolvedTask) {
876
+ throw new Error("No task specified and no active task in session.");
877
+ }
878
+ const repoRoot = targetRepo === "monorepo" ? resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot(projectRoot) : projectRoot;
879
+ const repoLabel = targetRepo === "monorepo" ? "Monorepo" : "Project";
880
+ if (!existsSync3(resolve3(repoRoot, ".git"))) {
881
+ throw new Error(`${repoLabel} repo not found at ${repoRoot}`);
882
+ }
883
+ const branchId = resolveTaskBranchId(projectRoot, resolvedTask);
884
+ const branchTarget = `rig/${branchId}`;
885
+ const current = branchName(projectRoot, repoRoot);
886
+ if (current === branchTarget) {
887
+ console.log(`${repoLabel} branch: already on ${branchTarget}`);
888
+ return;
889
+ }
890
+ const hasBranch = runCapture(gitCmd(projectRoot, repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branchTarget}`), projectRoot).exitCode === 0;
891
+ const cmd = hasBranch && current === "HEAD" ? gitCmd(projectRoot, repoRoot, "checkout", "-B", branchTarget) : hasBranch ? gitCmd(projectRoot, repoRoot, "checkout", branchTarget) : gitCmd(projectRoot, repoRoot, "checkout", "-b", branchTarget);
892
+ const checkout = runCapture(cmd, projectRoot);
893
+ if (checkout.exitCode !== 0) {
894
+ throw new Error(`Failed to sync ${repoLabel.toLowerCase()} branch: ${checkout.stderr || checkout.stdout}`);
895
+ }
896
+ const action = hasBranch && current === "HEAD" ? "reset" : hasBranch ? "checked out" : "created";
897
+ console.log(`${repoLabel} branch: ${action} ${branchTarget}`);
898
+ }
899
+ function gitCommit(options) {
900
+ const { projectRoot } = options;
901
+ const resolvedTask = options.taskId || safeCurrentTaskId(projectRoot);
902
+ const baseMessage = options.message || (resolvedTask ? `rig: ${resolvedTask}` : "rig: harness update");
903
+ const changedFilesManifest = resolvedTask && options.scoped === true ? refreshChangedFilesManifest(projectRoot, resolvedTask) : "";
904
+ const changedFiles = resolvedTask && options.scoped === true ? readChangedFilesManifest(projectRoot, resolvedTask) : resolvedTask ? taskData().changedFilesForTask(projectRoot, resolvedTask, false) : [];
905
+ if (options.target === "project" || options.target === "both") {
906
+ if (resolvedTask) {
907
+ gitSyncBranch(projectRoot, resolvedTask, "project");
908
+ }
909
+ const projectFiles = resolveScopedStageFilesForRepo(projectRoot, projectRoot, resolvedTask, changedFiles);
910
+ commitRepo(projectRoot, projectRoot, "project-rig", options.target === "both" ? `${baseMessage} [harness]` : baseMessage, options.allowEmpty, options.scoped === true, projectFiles, changedFilesManifest);
911
+ }
912
+ if (options.target === "monorepo" || options.target === "both") {
913
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot(projectRoot);
914
+ if (resolvedTask) {
915
+ gitSyncBranch(projectRoot, resolvedTask, "monorepo");
916
+ }
917
+ const monorepoFiles = resolveScopedStageFilesForRepo(projectRoot, monorepoRoot, resolvedTask, changedFiles);
918
+ commitRepo(projectRoot, monorepoRoot, "monorepo", options.target === "both" ? `${baseMessage} [monorepo]` : baseMessage, options.allowEmpty, options.scoped === true, monorepoFiles, changedFilesManifest);
919
+ }
920
+ }
921
+ function gitSnapshot(projectRoot, taskId, outputPath) {
922
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
923
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
924
+ const output = outputPath || (resolvedTask ? resolveArtifactSnapshot(projectRoot, resolvedTask) : resolve3(resolve3(projectRoot, ".rig", "state"), "git-state.txt"));
925
+ mkdirSync(dirname(output), { recursive: true });
926
+ const lines = ["# Git Snapshot", `timestamp: ${nowIso()}`];
927
+ if (resolvedTask) {
928
+ lines.push(`task: ${resolvedTask}`);
929
+ }
930
+ lines.push("");
931
+ lines.push(...snapshotRepo(projectRoot, "project-rig", projectRoot));
932
+ if (monorepoRoot && monorepoRoot !== projectRoot) {
933
+ lines.push(...snapshotRepo(projectRoot, "monorepo", monorepoRoot));
934
+ }
935
+ writeFileSync(output, `${lines.join(`
936
+ `)}
937
+ `, "utf-8");
938
+ return output;
939
+ }
940
+ function gitOpenPr(options) {
941
+ const gh = resolveGithubCliBinary({ scanPath: true });
942
+ if (!gh) {
943
+ throw new Error("gh CLI is required for open-pr. Install and authenticate with: gh auth login");
944
+ }
945
+ const taskId = options.taskId || safeCurrentTaskId(options.projectRoot);
946
+ const target = options.target || (taskId ? "monorepo" : "project");
947
+ let repoRoot = options.projectRoot;
948
+ let repoLabel = "project-rig";
949
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
950
+ if (target === "monorepo") {
951
+ repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot(options.projectRoot);
952
+ repoLabel = "monorepo";
953
+ if (taskId) {
954
+ gitSyncBranch(options.projectRoot, taskId, "monorepo");
955
+ }
956
+ } else if (taskId) {
957
+ gitSyncBranch(options.projectRoot, taskId, "project");
958
+ }
959
+ if (!existsSync3(resolve3(repoRoot, ".git"))) {
960
+ throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
961
+ }
962
+ const branch = branchName(options.projectRoot, repoRoot);
963
+ if (!branch || branch === "HEAD") {
964
+ throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
965
+ }
966
+ const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
967
+ const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
968
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
969
+ refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
970
+ let reviewer = (options.reviewer || "").trim();
971
+ let reviewerSource = reviewer ? "flag" : undefined;
972
+ if (!reviewer && taskId) {
973
+ reviewer = defaultReviewerForTask(options.projectRoot, taskId);
974
+ if (reviewer) {
975
+ reviewerSource = "task-config";
976
+ }
977
+ }
978
+ if (!reviewer) {
979
+ reviewer = inferReviewerFromChangedFiles(options.projectRoot, repoRoot, base, branch);
980
+ if (reviewer) {
981
+ reviewerSource = "changed-files";
982
+ }
983
+ }
984
+ if (!reviewer) {
985
+ reviewer = (process.env.RIG_PR_REVIEWER || "").trim();
986
+ if (reviewer) {
987
+ reviewerSource = "env";
988
+ }
989
+ }
990
+ let title = options.title || "";
991
+ if (!title) {
992
+ if (taskId) {
993
+ title = `rig: ${taskId}`;
994
+ } else {
995
+ title = `rig: update ${branch}`;
996
+ }
997
+ }
998
+ const body = options.body || [
999
+ "## Summary",
1000
+ "- Automated task output prepared in isolated runtime.",
1001
+ "",
1002
+ "## Task",
1003
+ `- beads: ${taskId || "n/a"}`,
1004
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
1005
+ "",
1006
+ "## Review",
1007
+ "- Completion verification will run validation, verifier review, and PR policy checks.",
1008
+ "- When repository policy allows it, Rig attempts an immediate strict-gated, head-locked merge after approval."
1009
+ ].join(`
1010
+ `);
1011
+ const preCheck = runCapture(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
1012
+ const preCheckEntry = preCheck.exitCode === 0 ? preCheck.stdout.trim() : "";
1013
+ if (preCheckEntry && preCheckEntry !== "null" && currentHeadMatchesMergedBase(options.projectRoot, repoRoot, base, networkRemote)) {
1014
+ const mergedPr = JSON.parse(preCheckEntry);
1015
+ console.log(`Branch ${branch} was already merged: ${mergedPr.url}`);
1016
+ const result2 = { url: mergedPr.url, target, repoLabel, branch, base };
1017
+ if (taskId)
1018
+ writePrMetadata(options.projectRoot, taskId, result2);
1019
+ return result2;
1020
+ }
1021
+ const pushArgs = gitCmd(options.projectRoot, repoRoot, "push", "-u", networkRemote, branch);
1022
+ const fetchResult = runCapture(gitCmd(options.projectRoot, repoRoot, "fetch", networkRemote, branch), repoRoot);
1023
+ if (fetchResult.exitCode === 0) {
1024
+ const remoteAhead = runCapture(gitCmd(options.projectRoot, repoRoot, "log", "--oneline", `HEAD..${networkRemote}/${branch}`), repoRoot);
1025
+ if (remoteAhead.exitCode === 0 && remoteAhead.stdout.trim()) {
1026
+ console.log(`Remote branch has diverged \u2014 force pushing task-owned branch ${branch} with --force-with-lease...`);
1027
+ pushArgs.splice(4, 0, "--force-with-lease");
1028
+ }
1029
+ }
1030
+ runOrThrow(options.projectRoot, pushArgs, `Failed to push branch ${branch} in ${repoLabel}`);
1031
+ const existing = runCapture(withGhRepo([gh, "pr", "list", "--state", "open", "--head", branch, "--json", "url", "--jq", ".[0].url"], repoNameWithOwner), repoRoot);
1032
+ const existingUrl = existing.exitCode === 0 ? existing.stdout.trim() : "";
1033
+ if (!existingUrl || existingUrl === "null") {
1034
+ const merged = runCapture(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
1035
+ const mergedEntry = merged.exitCode === 0 ? merged.stdout.trim() : "";
1036
+ if (mergedEntry && mergedEntry !== "null" && currentHeadMatchesMergedBase(options.projectRoot, repoRoot, base, networkRemote)) {
1037
+ const mergedPr = JSON.parse(mergedEntry);
1038
+ console.log(`Branch ${branch} was already merged: ${mergedPr.url}`);
1039
+ const result2 = { url: mergedPr.url, target, repoLabel, branch, base };
1040
+ if (taskId)
1041
+ writePrMetadata(options.projectRoot, taskId, result2);
1042
+ return result2;
1043
+ }
1044
+ }
1045
+ let prUrl = "";
1046
+ if (existingUrl && existingUrl !== "null") {
1047
+ prUrl = existingUrl;
1048
+ } else {
1049
+ const createArgs = [
1050
+ gh,
1051
+ "pr",
1052
+ "create",
1053
+ ...ghRepoArgs(repoNameWithOwner),
1054
+ "--base",
1055
+ base,
1056
+ "--head",
1057
+ branch,
1058
+ "--title",
1059
+ title,
1060
+ "--body",
1061
+ body
1062
+ ];
1063
+ if (options.draft) {
1064
+ createArgs.push("--draft");
1065
+ }
1066
+ const created = runCapture(createArgs, repoRoot);
1067
+ if (created.exitCode !== 0) {
1068
+ throw new Error(`Failed to create PR in ${repoLabel}: ${created.stderr || created.stdout}`);
1069
+ }
1070
+ prUrl = created.stdout.trim();
1071
+ }
1072
+ if (!prUrl) {
1073
+ throw new Error(`Failed to resolve PR URL for branch ${branch}.`);
1074
+ }
1075
+ assertPrHasNoGitConflicts(readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl), repoLabel, base);
1076
+ if (reviewer) {
1077
+ const edit = runCapture(withGhRepo([gh, "pr", "edit", prUrl, "--add-reviewer", reviewer], repoNameWithOwner), repoRoot);
1078
+ if (edit.exitCode !== 0) {
1079
+ throw new Error(`Failed to assign reviewer '${reviewer}': ${edit.stderr || edit.stdout}`);
1080
+ }
1081
+ }
1082
+ const result = {
1083
+ url: prUrl,
1084
+ ...reviewer ? { reviewer } : {},
1085
+ ...reviewerSource ? { reviewerSource } : {},
1086
+ target,
1087
+ repoLabel,
1088
+ branch,
1089
+ base
1090
+ };
1091
+ if (taskId) {
1092
+ writePrMetadata(options.projectRoot, taskId, result);
1093
+ }
1094
+ return result;
1095
+ }
1096
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
1097
+ const lines = [];
1098
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
1099
+ if (runId) {
1100
+ lines.push(`- Run: ${runId}`);
1101
+ }
1102
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
1103
+ if (closeout) {
1104
+ lines.push(`- ${closeout}`);
1105
+ }
1106
+ return lines;
1107
+ }
1108
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
1109
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
1110
+ if (sourceIssueId) {
1111
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
1112
+ if (match?.[1] && match[2]) {
1113
+ const sourceRepo = match[1];
1114
+ const issueNumber = match[2];
1115
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
1116
+ }
1117
+ }
1118
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
1119
+ }
1120
+ function resolveTaskBranchRef(projectRoot, taskId) {
1121
+ return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1122
+ }
1123
+ function readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl) {
1124
+ const view = runCapture(withGhRepo([
1125
+ gh,
1126
+ "pr",
1127
+ "view",
1128
+ prUrl,
1129
+ "--json",
1130
+ "state,isDraft,url,mergedAt,autoMergeRequest,mergeable,mergeStateStatus,reviewDecision,headRefOid,statusCheckRollup",
1131
+ "--jq",
1132
+ "."
1133
+ ], repoNameWithOwner), repoRoot);
1134
+ if (view.exitCode !== 0) {
1135
+ throw new Error(`Failed to inspect PR ${prUrl}: ${view.stderr || view.stdout}`);
1136
+ }
1137
+ try {
1138
+ const parsed = JSON.parse(view.stdout);
1139
+ return {
1140
+ state: parsed.state || "OPEN",
1141
+ isDraft: parsed.isDraft === true,
1142
+ url: typeof parsed.url === "string" ? parsed.url : prUrl,
1143
+ mergedAt: typeof parsed.mergedAt === "string" ? parsed.mergedAt : null,
1144
+ autoMergeRequest: parsed.autoMergeRequest ?? null,
1145
+ mergeable: typeof parsed.mergeable === "string" ? parsed.mergeable : "",
1146
+ mergeStateStatus: typeof parsed.mergeStateStatus === "string" ? parsed.mergeStateStatus : "",
1147
+ reviewDecision: typeof parsed.reviewDecision === "string" ? parsed.reviewDecision : "",
1148
+ headRefOid: typeof parsed.headRefOid === "string" ? parsed.headRefOid : null,
1149
+ statusCheckRollup: Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : []
1150
+ };
1151
+ } catch {
1152
+ return {
1153
+ state: "OPEN",
1154
+ isDraft: false,
1155
+ url: prUrl,
1156
+ mergedAt: null,
1157
+ autoMergeRequest: null,
1158
+ mergeable: "",
1159
+ mergeStateStatus: "",
1160
+ reviewDecision: "",
1161
+ headRefOid: null,
1162
+ statusCheckRollup: []
1163
+ };
1164
+ }
1165
+ }
1166
+ function hasSatisfiedStatusChecks(prState) {
1167
+ if (prState.statusCheckRollup.length === 0) {
1168
+ return false;
1169
+ }
1170
+ return prState.statusCheckRollup.every((entry) => {
1171
+ if (entry.__typename === "CheckRun") {
1172
+ const status = entry.status?.toUpperCase() || "";
1173
+ const conclusion = entry.conclusion?.toUpperCase() || "";
1174
+ return status === "COMPLETED" && (conclusion === "SUCCESS" || conclusion === "SKIPPED" || conclusion === "NEUTRAL");
1175
+ }
1176
+ if (entry.__typename === "StatusContext") {
1177
+ return (entry.state?.toUpperCase() || "") === "SUCCESS";
1178
+ }
1179
+ return false;
1180
+ });
1181
+ }
1182
+ function canAdminMergeApprovedPr(prState) {
1183
+ return prState.state === "OPEN" && prState.autoMergeRequest !== null && prState.mergeable.toUpperCase() === "MERGEABLE" && prState.reviewDecision.toUpperCase() === "APPROVED" && hasSatisfiedStatusChecks(prState);
1184
+ }
1185
+ function gitMergePr(options) {
1186
+ const gh = resolveGithubCliBinary({ scanPath: true });
1187
+ if (!gh) {
1188
+ throw new Error("gh CLI is required for merge-pr. Install and authenticate with: gh auth login");
1189
+ }
1190
+ const repoRoot = resolveRepoRoot(options.projectRoot, options.pr.target);
1191
+ const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
1192
+ if (!existsSync3(resolve3(repoRoot, ".git"))) {
1193
+ throw new Error(`Repository not available for merge-pr target ${options.pr.target}: ${repoRoot}`);
1194
+ }
1195
+ const prState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
1196
+ const state = prState.state;
1197
+ const isDraft = prState.isDraft;
1198
+ assertPrHasNoGitConflicts(prState, options.pr.repoLabel, options.pr.base);
1199
+ if (state === "MERGED") {
1200
+ console.log(`PR already merged (${options.pr.repoLabel}): ${options.pr.url}`);
1201
+ return { status: "already-merged", url: options.pr.url };
1202
+ }
1203
+ if (state !== "OPEN") {
1204
+ throw new Error(`Cannot merge PR ${options.pr.url}: state is ${state}.`);
1205
+ }
1206
+ if (isDraft) {
1207
+ throw new Error(`Cannot merge draft PR ${options.pr.url}.`);
1208
+ }
1209
+ const mergeArgs = withGhRepo([gh, "pr", "merge", options.pr.url], repoNameWithOwner);
1210
+ const method = options.method || "squash";
1211
+ mergeArgs.push(method === "merge" ? "--merge" : method === "rebase" ? "--rebase" : "--squash");
1212
+ mergeArgs.push("--match-head-commit", options.matchHeadCommit);
1213
+ if (options.deleteBranch !== false) {
1214
+ mergeArgs.push("--delete-branch");
1215
+ }
1216
+ const directMerge = runCapture(mergeArgs, repoRoot);
1217
+ if (directMerge.exitCode === 0) {
1218
+ console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
1219
+ return { status: "merged", url: options.pr.url };
1220
+ }
1221
+ const postDirectState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
1222
+ if (canAdminMergeApprovedPr(postDirectState)) {
1223
+ const adminMergeArgs = [...mergeArgs, "--admin"];
1224
+ const adminMerge = runCapture(adminMergeArgs, repoRoot);
1225
+ if (adminMerge.exitCode === 0) {
1226
+ const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
1227
+ if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
1228
+ console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
1229
+ return { status: "merged", url: options.pr.url };
1230
+ }
1231
+ throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
1232
+ }
1233
+ const adminMergeMessage = `${adminMerge.stderr}
1234
+ ${adminMerge.stdout}`.trim();
1235
+ if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
1236
+ throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
1237
+ }
1238
+ }
1239
+ const directMergeMessage = `${directMerge.stderr}
1240
+ ${directMerge.stdout}`.trim();
1241
+ throw new Error(`Failed to merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${directMergeMessage}`);
1242
+ }
1243
+ function assertPrHasNoGitConflicts(prState, repoLabel, baseRef) {
1244
+ const mergeable = prState.mergeable.toUpperCase();
1245
+ const mergeStateStatus = prState.mergeStateStatus.toUpperCase();
1246
+ if (mergeable === "CONFLICTING" || mergeStateStatus === "DIRTY") {
1247
+ throw new Error(`PR ${prState.url || "unknown"} conflicts with ${baseRef} in ${repoLabel} (mergeable=${prState.mergeable || "unknown"}, mergeStateStatus=${prState.mergeStateStatus || "unknown"}). Rebase or merge ${baseRef} and resolve conflicts before completion-verification.`);
1248
+ }
1249
+ }
1250
+ function writePrMetadata(projectRoot, taskId, result) {
1251
+ const dir = taskData().artifactDirForId(projectRoot, taskId);
1252
+ mkdirSync(dir, { recursive: true });
1253
+ const path = resolve3(dir, "pr-state.json");
1254
+ let prs = {};
1255
+ if (existsSync3(path)) {
1256
+ try {
1257
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
1258
+ if (parsed && typeof parsed === "object" && parsed.prs && typeof parsed.prs === "object") {
1259
+ prs = parsed.prs;
1260
+ }
1261
+ } catch {
1262
+ prs = {};
1263
+ }
1264
+ }
1265
+ prs[result.target] = result;
1266
+ const primary = prs.monorepo || prs.project;
1267
+ const artifact = {
1268
+ task_id: taskId,
1269
+ prs,
1270
+ ...primary || {},
1271
+ updated_at: nowIso()
1272
+ };
1273
+ writeFileSync(path, `${JSON.stringify(artifact, null, 2)}
1274
+ `, "utf-8");
1275
+ }
1276
+ function readPrMetadata(projectRoot, taskId) {
1277
+ const path = resolve3(taskData().artifactDirForId(projectRoot, taskId), "pr-state.json");
1278
+ if (!existsSync3(path)) {
1279
+ return [];
1280
+ }
1281
+ try {
1282
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
1283
+ if (!parsed || typeof parsed !== "object") {
1284
+ return [];
1285
+ }
1286
+ if (parsed.prs && typeof parsed.prs === "object") {
1287
+ return Object.values(parsed.prs).filter(isGitOpenPrResult);
1288
+ }
1289
+ return isGitOpenPrResult(parsed) ? [parsed] : [];
1290
+ } catch {
1291
+ return [];
1292
+ }
1293
+ }
1294
+ function resolveArtifactSnapshot(projectRoot, taskId) {
1295
+ return resolve3(taskData().artifactDirForId(projectRoot, taskId), "git-state.txt");
1296
+ }
1297
+ function isGitOpenPrResult(value) {
1298
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1299
+ return false;
1300
+ }
1301
+ const record = value;
1302
+ return typeof record.url === "string" && typeof record.branch === "string" && typeof record.base === "string" && (record.target === "project" || record.target === "monorepo") && typeof record.repoLabel === "string";
1303
+ }
1304
+ function resolveRepoRoot(projectRoot, target) {
1305
+ return target === "monorepo" ? resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot(projectRoot) : projectRoot;
1306
+ }
1307
+ function ensureFullGitHistory(projectRoot, repoRoot, remoteName = "origin") {
1308
+ const shallow = runCapture(gitCmd(projectRoot, repoRoot, "rev-parse", "--is-shallow-repository"), projectRoot);
1309
+ if (shallow.exitCode !== 0 || shallow.stdout.trim() !== "true") {
1310
+ return;
1311
+ }
1312
+ const unshallow = runCapture(gitCmd(projectRoot, repoRoot, "fetch", "--unshallow", "--tags", remoteName), projectRoot);
1313
+ if (unshallow.exitCode === 0) {
1314
+ return;
1315
+ }
1316
+ const output = `${unshallow.stderr}
1317
+ ${unshallow.stdout}`.trim();
1318
+ if (/--unshallow on a complete repository|does not make sense/i.test(output)) {
1319
+ return;
1320
+ }
1321
+ throw new Error(`Failed to expand git history for ${repoRoot}: ${output}`);
1322
+ }
1323
+ function refreshRemoteBaseRef(projectRoot, repoRoot, baseRef, repoNameWithOwner = "") {
1324
+ const remoteName = resolveNetworkRemoteName(projectRoot, repoRoot, repoNameWithOwner || resolveRepoNameWithOwner(projectRoot, repoRoot));
1325
+ const remoteUrl = runCapture(gitCmd(projectRoot, repoRoot, "remote", "get-url", remoteName), projectRoot);
1326
+ if (remoteUrl.exitCode !== 0) {
1327
+ return "";
1328
+ }
1329
+ ensureFullGitHistory(projectRoot, repoRoot, remoteName);
1330
+ const fetch2 = runCapture(gitCmd(projectRoot, repoRoot, "fetch", "--prune", "--tags", remoteName, `+refs/heads/${baseRef}:refs/remotes/${remoteName}/${baseRef}`), projectRoot);
1331
+ if (fetch2.exitCode !== 0) {
1332
+ throw new Error(`Failed to refresh ${remoteName}/${baseRef} at ${repoRoot}: ${fetch2.stderr || fetch2.stdout}`);
1333
+ }
1334
+ return remoteName;
1335
+ }
1336
+ function currentHeadMatchesMergedBase(projectRoot, repoRoot, baseRef, remoteName = "origin") {
1337
+ const activeRemote = refreshRemoteBaseRef(projectRoot, repoRoot, baseRef) || remoteName;
1338
+ const remoteBase = `${activeRemote}/${baseRef}`;
1339
+ const hasRemoteBase = runCapture(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", remoteBase), repoRoot).exitCode === 0;
1340
+ const targetRef = hasRemoteBase ? remoteBase : baseRef;
1341
+ if (runCapture(gitCmd(projectRoot, repoRoot, "merge-base", "--is-ancestor", "HEAD", targetRef), repoRoot).exitCode === 0) {
1342
+ return true;
1343
+ }
1344
+ return runCapture(gitCmd(projectRoot, repoRoot, "diff", "--quiet", "HEAD", targetRef, "--"), repoRoot).exitCode === 0;
1345
+ }
1346
+ function defaultReviewerForTask(projectRoot, taskId) {
1347
+ if (!taskId) {
1348
+ return "";
1349
+ }
1350
+ const entry = taskData().readTaskConfig(projectRoot)[taskId];
1351
+ const reviewer = entry?.reviewer;
1352
+ if (typeof reviewer === "string" && reviewer.trim()) {
1353
+ return reviewer.trim();
1354
+ }
1355
+ const responsibleReviewer = entry?.responsible_reviewer;
1356
+ if (typeof responsibleReviewer === "string" && responsibleReviewer.trim()) {
1357
+ return responsibleReviewer.trim();
1358
+ }
1359
+ return "";
1360
+ }
1361
+ function resolveRepoNameWithOwner(projectRoot, repoRoot) {
1362
+ const explicit = normalizeGithubRepoNameWithOwner(process.env.GH_REPO || "");
1363
+ if (explicit) {
1364
+ return explicit;
1365
+ }
1366
+ const visited = new Set;
1367
+ return resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, repoRoot, repoRoot, visited);
1368
+ }
1369
+ function resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, gitRoot, cwd, visited) {
1370
+ const normalizedGitRoot = resolve3(gitRoot);
1371
+ if (visited.has(normalizedGitRoot)) {
1372
+ return "";
1373
+ }
1374
+ visited.add(normalizedGitRoot);
1375
+ const remotes = listGitRemotes(projectRoot, gitRoot, cwd);
1376
+ for (const remote of remotes) {
1377
+ const urls = listGitRemoteUrls(projectRoot, gitRoot, cwd, remote);
1378
+ for (const url of urls) {
1379
+ const direct = normalizeGithubRepoNameWithOwner(url);
1380
+ if (direct) {
1381
+ return direct;
1382
+ }
1383
+ const localGitRoot = resolveLocalGitRemoteRoot(url, gitRoot);
1384
+ if (!localGitRoot) {
1385
+ continue;
1386
+ }
1387
+ const viaMirror = resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, localGitRoot, cwd, visited);
1388
+ if (viaMirror) {
1389
+ return viaMirror;
1390
+ }
1391
+ }
1392
+ }
1393
+ return "";
1394
+ }
1395
+ function listGitRemotes(projectRoot, gitRoot, cwd) {
1396
+ const result = gitQuery(projectRoot, gitRoot, cwd, "remote");
1397
+ if (result.exitCode !== 0) {
1398
+ return [];
1399
+ }
1400
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1401
+ }
1402
+ function listGitRemoteUrls(projectRoot, gitRoot, cwd, remote) {
1403
+ const result = gitQuery(projectRoot, gitRoot, cwd, "remote", "get-url", "--all", remote);
1404
+ if (result.exitCode !== 0) {
1405
+ return [];
1406
+ }
1407
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1408
+ }
1409
+ function resolveNetworkRemoteName(projectRoot, repoRoot, repoNameWithOwner) {
1410
+ const remotes = listGitRemotes(projectRoot, repoRoot, repoRoot);
1411
+ if (remotes.length === 0) {
1412
+ return "origin";
1413
+ }
1414
+ if (!repoNameWithOwner) {
1415
+ return remotes.includes("origin") ? "origin" : remotes[0];
1416
+ }
1417
+ const expectedRepo = normalizeGithubRepoNameWithOwner(repoNameWithOwner).toLowerCase();
1418
+ let directMatch = "";
1419
+ for (const remote of remotes) {
1420
+ const urls = listGitRemoteUrls(projectRoot, repoRoot, repoRoot, remote);
1421
+ for (const url of urls) {
1422
+ const direct = normalizeGithubRepoNameWithOwner(url);
1423
+ if (direct && direct.toLowerCase() === expectedRepo) {
1424
+ if (remote === "github") {
1425
+ return remote;
1426
+ }
1427
+ if (!directMatch) {
1428
+ directMatch = remote;
1429
+ }
1430
+ }
1431
+ }
1432
+ }
1433
+ if (remotes.includes("github")) {
1434
+ return "github";
1435
+ }
1436
+ if (directMatch) {
1437
+ return directMatch;
1438
+ }
1439
+ return remotes.includes("origin") ? "origin" : remotes[0];
1440
+ }
1441
+ function gitQuery(projectRoot, gitRoot, cwd, ...args) {
1442
+ const gitArgs = existsSync3(resolve3(gitRoot, ".git")) ? gitCmd(projectRoot, gitRoot, ...args) : [resolveHostGitBinary(), "--git-dir", gitRoot, ...args];
1443
+ return runCapture(gitArgs, cwd, projectRoot);
1444
+ }
1445
+ function resolveLocalGitRemoteRoot(remoteUrl, gitRoot) {
1446
+ const normalized = remoteUrl.trim();
1447
+ if (!normalized) {
1448
+ return "";
1449
+ }
1450
+ let candidate = normalized;
1451
+ if (normalized.startsWith("file://")) {
1452
+ try {
1453
+ candidate = fileURLToPath(normalized);
1454
+ } catch {
1455
+ return "";
1456
+ }
1457
+ } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(normalized) || /^[^@]+@[^:]+:.+$/.test(normalized)) {
1458
+ return "";
1459
+ } else if (!isAbsolute(normalized)) {
1460
+ candidate = resolve3(gitRoot, normalized);
1461
+ }
1462
+ return existsSync3(candidate) ? candidate : "";
1463
+ }
1464
+ function normalizeGithubRepoNameWithOwner(value) {
1465
+ const normalized = value.trim();
1466
+ if (!normalized) {
1467
+ return "";
1468
+ }
1469
+ const scpMatch = normalized.match(/^(?:ssh:\/\/)?git@github\.com[:/](.+?)(?:\.git)?$/i);
1470
+ if (scpMatch?.[1]) {
1471
+ return scpMatch[1].replace(/^\/+|\/+$/g, "");
1472
+ }
1473
+ const httpMatch = normalized.match(/^https?:\/\/github\.com\/(.+?)(?:\.git)?(?:\/)?$/i);
1474
+ if (httpMatch?.[1]) {
1475
+ return httpMatch[1].replace(/^\/+|\/+$/g, "");
1476
+ }
1477
+ const bareMatch = normalized.match(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/);
1478
+ return bareMatch ? bareMatch[0] : "";
1479
+ }
1480
+ function ghRepoArgs(repoNameWithOwner) {
1481
+ return repoNameWithOwner ? ["-R", repoNameWithOwner] : [];
1482
+ }
1483
+ function withGhRepo(command, repoNameWithOwner) {
1484
+ if (!repoNameWithOwner || command.length < 3) {
1485
+ return command;
1486
+ }
1487
+ return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
1488
+ }
1489
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
1490
+ const remote = remoteName || "origin";
1491
+ const symbolic = runCapture(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
1492
+ if (symbolic.exitCode === 0) {
1493
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
1494
+ if (ref && ref !== "HEAD") {
1495
+ return ref;
1496
+ }
1497
+ }
1498
+ const lsRemote = runCapture(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
1499
+ if (lsRemote.exitCode === 0) {
1500
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
1501
+ if (match?.[1]) {
1502
+ return match[1];
1503
+ }
1504
+ }
1505
+ const gh = resolveGithubCliBinary({ scanPath: true });
1506
+ if (gh && repoNameWithOwner) {
1507
+ const api = runCapture(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
1508
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
1509
+ if (branch) {
1510
+ return branch;
1511
+ }
1512
+ }
1513
+ return fallback;
1514
+ }
1515
+ function inferProjectBase(projectRoot, fallback) {
1516
+ const containing = runCapture(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
1517
+ if (containing.exitCode !== 0) {
1518
+ return fallback;
1519
+ }
1520
+ const candidates = containing.stdout.split(/\r?\n/).map((line) => line.replace(/^\*/, "").trim()).filter(Boolean).map((line) => line.replace(/^origin\//, "")).filter((line) => line !== "HEAD" && !line.startsWith("rig/"));
1521
+ return candidates[0] || fallback;
1522
+ }
1523
+ function currentGithubLogin(repoRoot) {
1524
+ const gh = resolveGithubCliBinary({ scanPath: true });
1525
+ if (!gh) {
1526
+ return "";
1527
+ }
1528
+ const result = runCapture([gh, "api", "user", "--jq", ".login"], repoRoot);
1529
+ return result.exitCode === 0 ? result.stdout.trim() : "";
1530
+ }
1531
+ function collectPrChangedFiles(projectRoot, repoRoot, baseRef, branchRef) {
1532
+ const repoNameWithOwner = resolveRepoNameWithOwner(projectRoot, repoRoot);
1533
+ const remoteName = refreshRemoteBaseRef(projectRoot, repoRoot, baseRef, repoNameWithOwner);
1534
+ const hasRemoteBase = remoteName ? runCapture(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", `${remoteName}/${baseRef}`), projectRoot).exitCode === 0 : false;
1535
+ const hasLocalBase = runCapture(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", baseRef), projectRoot).exitCode === 0;
1536
+ let changed = "";
1537
+ if (hasRemoteBase) {
1538
+ changed = runCapture(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `${remoteName}/${baseRef}...${branchRef}`), projectRoot).stdout;
1539
+ } else if (hasLocalBase) {
1540
+ changed = runCapture(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `${baseRef}...${branchRef}`), projectRoot).stdout;
1541
+ } else {
1542
+ const fallback = runCapture(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `HEAD~1..${branchRef}`), projectRoot);
1543
+ changed = fallback.exitCode === 0 ? fallback.stdout : runCapture(gitCmd(projectRoot, repoRoot, "diff", "--name-only"), projectRoot).stdout;
1544
+ }
1545
+ return changed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 60);
1546
+ }
1547
+ function inferReviewerFromChangedFiles(projectRoot, repoRoot, baseRef, branchRef) {
1548
+ const repoNameWithOwner = resolveRepoNameWithOwner(projectRoot, repoRoot);
1549
+ if (!repoNameWithOwner) {
1550
+ return "";
1551
+ }
1552
+ const actorLogin = currentGithubLogin(repoRoot);
1553
+ const changedFiles = collectPrChangedFiles(projectRoot, repoRoot, baseRef, branchRef);
1554
+ if (changedFiles.length === 0) {
1555
+ return "";
1556
+ }
1557
+ const counts = new Map;
1558
+ for (const path of changedFiles) {
1559
+ const result = runCapture([
1560
+ resolveGithubCliBinary({ scanPath: true }) || "gh",
1561
+ "api",
1562
+ `repos/${repoNameWithOwner}/commits`,
1563
+ "-f",
1564
+ `path=${path}`,
1565
+ "-f",
1566
+ `sha=${baseRef}`,
1567
+ "-f",
1568
+ "per_page=1",
1569
+ "--jq",
1570
+ ".[0].author.login // empty"
1571
+ ], repoRoot);
1572
+ const author = result.exitCode === 0 ? result.stdout.trim() : "";
1573
+ if (!author || author === actorLogin) {
1574
+ continue;
1575
+ }
1576
+ counts.set(author, (counts.get(author) || 0) + 1);
1577
+ }
1578
+ let best = "";
1579
+ let max = -1;
1580
+ for (const [author, count] of counts.entries()) {
1581
+ if (count > max || count === max && author < best) {
1582
+ best = author;
1583
+ max = count;
1584
+ }
1585
+ }
1586
+ return best;
1587
+ }
1588
+ function snapshotRepo(projectRoot, label, repo) {
1589
+ if (!existsSync3(resolve3(repo, ".git"))) {
1590
+ return [`## ${label}`, `repo: ${repo}`, "status: unavailable", ""];
1591
+ }
1592
+ const status = runCapture(gitCmd(projectRoot, repo, "status", "--short"), projectRoot).stdout.trim();
1593
+ const branch = branchName(projectRoot, repo);
1594
+ const head = runCapture(gitCmd(projectRoot, repo, "rev-parse", "HEAD"), projectRoot).stdout.trim();
1595
+ return [
1596
+ `## ${label}`,
1597
+ `repo: ${repo}`,
1598
+ `branch: ${branch}`,
1599
+ `head: ${head}`,
1600
+ "status:",
1601
+ status || "(clean)",
1602
+ ""
1603
+ ];
1604
+ }
1605
+ function commitRepo(projectRoot, repo, label, message, allowEmpty, scoped, files, changedFilesManifest) {
1606
+ if (!existsSync3(resolve3(repo, ".git"))) {
1607
+ console.log(`Skipping ${label}: repo not available (${repo})`);
1608
+ return;
1609
+ }
1610
+ const scopedFiles = (files || []).filter(Boolean).filter((file) => !pathResolvesBeyondSymlink(repo, file));
1611
+ const repoChanges = changeCount(projectRoot, repo);
1612
+ if (scopedFiles.length === 0 && repoChanges === 0 && !allowEmpty) {
1613
+ console.log(`Skipping ${label}: no changes to commit.`);
1614
+ return;
1615
+ }
1616
+ if (scopedFiles.length > 0) {
1617
+ const indexFiles = new Set(runCapture(gitCmd(projectRoot, repo, "ls-files"), projectRoot).stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
1618
+ const stageable = scopedFiles.filter((file) => indexFiles.has(file) || existsSync3(resolve3(repo, file)));
1619
+ if (stageable.length === 0) {
1620
+ console.log(`Skipping ${label}: collected change list matched no stageable paths in ${repo}.`);
1621
+ return;
1622
+ }
1623
+ const pathspecFile = resolve3(tmpdir(), `rig-stage-${process.pid}-${Date.now()}.txt`);
1624
+ writeFileSync(pathspecFile, `${stageable.join(`
1625
+ `)}
1626
+ `, "utf-8");
1627
+ try {
1628
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, "add", `--pathspec-from-file=${pathspecFile}`), `Failed to stage changes for ${label}`);
1629
+ } finally {
1630
+ try {
1631
+ unlinkSync(pathspecFile);
1632
+ } catch {}
1633
+ }
1634
+ } else {
1635
+ const addArgs = buildStageAddArgs(repo, scopedFiles, scoped);
1636
+ if (addArgs) {
1637
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, ...addArgs), `Failed to stage changes for ${label}`);
1638
+ }
1639
+ }
1640
+ const stagedChanges = stagedChangeCount(projectRoot, repo);
1641
+ if (stagedChanges === 0) {
1642
+ if (allowEmpty) {
1643
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, "commit", "--allow-empty", "-m", message), `Failed to commit ${label}`);
1644
+ console.log(`Committed ${label}: ${message}`);
1645
+ return;
1646
+ }
1647
+ if (scoped && repoChanges > 0) {
1648
+ const manifestHint = changedFilesManifest ? ` Refresh ${changedFilesManifest} and retry, or use --all/--unscoped intentionally.` : "";
1649
+ throw new Error(`Scoped commit for ${label} resolved no stageable files.${manifestHint}`);
1650
+ }
1651
+ console.log(`Skipping ${label}: no stageable changes to commit.`);
1652
+ return;
1653
+ }
1654
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, "commit", ...allowEmpty ? ["--allow-empty"] : [], "-m", message), `Failed to commit ${label}`);
1655
+ console.log(`Committed ${label}: ${message}`);
1656
+ }
1657
+ function readChangedFilesManifest(projectRoot, taskId) {
1658
+ const manifestPath = resolve3(taskData().artifactDirForId(projectRoot, taskId), "changed-files.txt");
1659
+ if (!existsSync3(manifestPath)) {
1660
+ return [];
1661
+ }
1662
+ const files = readFileSync2(manifestPath, "utf-8").split(/\r?\n/).map((line) => normalizeChangedFilePath(line)).filter(Boolean);
1663
+ return [...new Set(files)];
1664
+ }
1665
+ function refreshChangedFilesManifest(projectRoot, taskId) {
1666
+ const manifestPath = resolve3(taskData().artifactDirForId(projectRoot, taskId), "changed-files.txt");
1667
+ mkdirSync(dirname(manifestPath), { recursive: true });
1668
+ const changedFiles = taskData().changedFilesForTask(projectRoot, taskId, true);
1669
+ writeFileSync(manifestPath, `${changedFiles.join(`
1670
+ `)}
1671
+ `, "utf-8");
1672
+ return manifestPath;
1673
+ }
1674
+ function normalizeChangedFilePath(file) {
1675
+ return file.trim().replace(/\\/g, "/").replace(/^\.\//, "");
1676
+ }
1677
+ function buildStageAddArgs(repoRoot, files, scoped) {
1678
+ if (files.length > 0) {
1679
+ return ["add", "--", ...files];
1680
+ }
1681
+ if (scoped) {
1682
+ return null;
1683
+ }
1684
+ return ["add", "-A", "--", ".", ...stageExcludePathspecs(repoRoot)];
1685
+ }
1686
+ function resolveScopedStageFilesForRepo(projectRoot, repoRoot, taskId, files) {
1687
+ const resolvedManifestFiles = resolveScopedFilesForRepo(projectRoot, repoRoot, files);
1688
+ if (resolvedManifestFiles.length > 0 || !taskId) {
1689
+ return resolvedManifestFiles;
1690
+ }
1691
+ return resolveChangedTaskArtifactFiles(projectRoot, repoRoot, taskId);
1692
+ }
1693
+ function resolveScopedFilesForRepo(projectRoot, repoRoot, files) {
1694
+ const resolvedFiles = [];
1695
+ const seen = new Set;
1696
+ for (const file of files) {
1697
+ const candidate = resolveScopedRepoPath(repoRoot, file);
1698
+ if (!candidate || seen.has(candidate) || pathResolvesBeyondSymlink(repoRoot, candidate)) {
1699
+ continue;
1700
+ }
1701
+ if (!repoHasPathChange(projectRoot, repoRoot, candidate)) {
1702
+ continue;
1703
+ }
1704
+ seen.add(candidate);
1705
+ resolvedFiles.push(candidate);
1706
+ }
1707
+ return resolvedFiles;
1708
+ }
1709
+ function resolveChangedTaskArtifactFiles(projectRoot, repoRoot, taskId) {
1710
+ const safeTaskId = safePathSegment(taskId, { fallback: "task", maxLength: 96 });
1711
+ const artifactPrefix = `artifacts/${safeTaskId}/`;
1712
+ const resolvedFiles = [];
1713
+ const seen = new Set;
1714
+ for (const file of collectRepoPendingFiles(projectRoot, repoRoot)) {
1715
+ if (!file.startsWith(artifactPrefix)) {
1716
+ continue;
1717
+ }
1718
+ const artifactRelativePath = file.slice(artifactPrefix.length);
1719
+ if (!TASK_ARTIFACT_STAGE_FALLBACK.has(artifactRelativePath)) {
1720
+ continue;
1721
+ }
1722
+ if (seen.has(file) || pathResolvesBeyondSymlink(repoRoot, file)) {
1723
+ continue;
1724
+ }
1725
+ seen.add(file);
1726
+ resolvedFiles.push(file);
1727
+ }
1728
+ return resolvedFiles.sort();
1729
+ }
1730
+ function collectRepoPendingFiles(projectRoot, repoRoot) {
1731
+ const files = new Set;
1732
+ for (const args of [
1733
+ ["diff", "--name-only"],
1734
+ ["diff", "--cached", "--name-only"],
1735
+ ["ls-files", "--others", "--exclude-standard"]
1736
+ ]) {
1737
+ const result = runCapture(gitCmd(projectRoot, repoRoot, ...args), projectRoot);
1738
+ if (result.exitCode !== 0) {
1739
+ continue;
1740
+ }
1741
+ for (const line of result.stdout.split(/\r?\n/)) {
1742
+ const normalized = normalizeChangedFilePath(line);
1743
+ if (!normalized) {
1744
+ continue;
1745
+ }
1746
+ files.add(normalized);
1747
+ }
1748
+ }
1749
+ return [...files].sort();
1750
+ }
1751
+ function resolveScopedRepoPath(repoRoot, file) {
1752
+ const normalized = normalizeChangedFilePath(file);
1753
+ if (!normalized) {
1754
+ return "";
1755
+ }
1756
+ const rules = getScopeRules();
1757
+ if (rules?.stripPrefixes) {
1758
+ let result = normalized;
1759
+ for (const prefix of rules.stripPrefixes) {
1760
+ if (result.startsWith(prefix)) {
1761
+ result = result.slice(prefix.length);
1762
+ }
1763
+ }
1764
+ return result;
1765
+ }
1766
+ return normalized;
1767
+ }
1768
+ function repoHasPathChange(projectRoot, repoRoot, relativePath) {
1769
+ const result = runCapture(gitCmd(projectRoot, repoRoot, "status", "--short", "--", relativePath), projectRoot);
1770
+ return result.exitCode === 0 && result.stdout.trim().length > 0;
1771
+ }
1772
+ function stageExcludePathspecs(repoRoot) {
1773
+ const patterns = existsSync3(resolve3(repoRoot, ".rig", "task-config.json")) ? [...TASK_RUNTIME_STAGE_EXCLUDES, ...GENERATED_STAGE_EXCLUDES] : [".rig/**", ...GENERATED_STAGE_EXCLUDES];
1774
+ return patterns.map((pattern) => `:(glob,exclude)${pattern}`);
1775
+ }
1776
+ function pathResolvesBeyondSymlink(repoRoot, relativePath) {
1777
+ const parts = relativePath.split("/").filter(Boolean);
1778
+ if (parts.length <= 1) {
1779
+ return false;
1780
+ }
1781
+ let current = repoRoot;
1782
+ for (let index = 0;index < parts.length - 1; index += 1) {
1783
+ current = resolve3(current, parts[index]);
1784
+ try {
1785
+ if (lstatSync(current).isSymbolicLink()) {
1786
+ return true;
1787
+ }
1788
+ } catch {
1789
+ return false;
1790
+ }
1791
+ }
1792
+ return false;
1793
+ }
1794
+ function printRepoStatus(projectRoot, label, repo, expectedBranch) {
1795
+ if (!existsSync3(resolve3(repo, ".git"))) {
1796
+ console.log(`${label}: unavailable (${repo})`);
1797
+ return;
1798
+ }
1799
+ const branch = branchName(projectRoot, repo);
1800
+ const changes = changeCount(projectRoot, repo);
1801
+ console.log(`${label}:`);
1802
+ console.log(` branch: ${branch || "unknown"}`);
1803
+ console.log(` changed files: ${changes}`);
1804
+ if (expectedBranch && label !== "project-rig" && branch !== expectedBranch) {
1805
+ console.log(` warning: branch mismatch (expected ${expectedBranch})`);
1806
+ }
1807
+ }
1808
+ function resolveTaskBranchId(projectRoot, taskId) {
1809
+ if (/^bd-[a-z0-9-]+$/.test(taskId)) {
1810
+ return taskId;
1811
+ }
1812
+ const normalizedTaskId = taskData().lookupTask(projectRoot, taskId);
1813
+ if (normalizedTaskId) {
1814
+ return normalizedTaskId;
1815
+ }
1816
+ const currentTask = taskData().currentTaskId(projectRoot);
1817
+ if (currentTask && currentTask === taskId) {
1818
+ return currentTask;
1819
+ }
1820
+ const runtimeIdFromEnv = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
1821
+ if (runtimeIdFromEnv.startsWith("task-") && runtimeIdFromEnv.length > "task-".length) {
1822
+ return runtimeIdFromEnv.slice("task-".length);
1823
+ }
1824
+ try {
1825
+ const runtimeIdFromContext = loadRuntimeContextFromEnv()?.runtimeId || "";
1826
+ if (runtimeIdFromContext.startsWith("task-") && runtimeIdFromContext.length > "task-".length) {
1827
+ return runtimeIdFromContext.slice("task-".length);
1828
+ }
1829
+ } catch {}
1830
+ const artifactDir = taskData().artifactDirForId(projectRoot, taskId);
1831
+ if (existsSync3(artifactDir)) {
1832
+ return taskId;
1833
+ }
1834
+ throw new Error(`Unknown task id: ${taskId}`);
1835
+ }
1836
+ function branchName(projectRoot, repo) {
1837
+ return runCapture(gitCmd(projectRoot, repo, "rev-parse", "--abbrev-ref", "HEAD"), projectRoot).stdout.trim();
1838
+ }
1839
+ function changeCount(projectRoot, repo) {
1840
+ const status = runCapture(gitCmd(projectRoot, repo, "status", "--short"), projectRoot).stdout.trim();
1841
+ return status ? status.split(/\r?\n/).filter(Boolean).length : 0;
1842
+ }
1843
+ function stagedChangeCount(projectRoot, repo) {
1844
+ const staged = runCapture(gitCmd(projectRoot, repo, "diff", "--cached", "--name-only"), projectRoot).stdout.trim();
1845
+ return staged ? staged.split(/\r?\n/).filter(Boolean).length : 0;
1846
+ }
1847
+ function runOrThrow(projectRoot, command, errorPrefix) {
1848
+ const result = runCapture(command, projectRoot);
1849
+ if (result.exitCode !== 0) {
1850
+ throw new Error(`${errorPrefix}:
1851
+ ${result.stderr || result.stdout}`);
1852
+ }
1853
+ }
1854
+ function runCapture(command, cwd, projectRoot = cwd) {
1855
+ return baseRunCapture(command, cwd, runtimeGitEnv(projectRoot));
1856
+ }
1857
+ function runtimeGitEnv(projectRoot) {
1858
+ const { ctx, runtimeRoot } = resolveRuntimeMetadata(projectRoot);
1859
+ const runtimeHome = runtimeRoot ? resolve3(runtimeRoot, "home") : "";
1860
+ const runtimeTmp = runtimeRoot ? resolve3(runtimeRoot, "tmp") : "";
1861
+ const runtimeCache = runtimeRoot ? resolve3(runtimeRoot, "cache") : "";
1862
+ const runtimeKnownHosts = runtimeHome ? resolve3(runtimeHome, ".ssh", "known_hosts") : "";
1863
+ const runtimeKey = runtimeHome ? resolve3(runtimeHome, ".ssh", "rig-agent-key") : "";
1864
+ const env = {};
1865
+ if (ctx?.workspaceDir) {
1866
+ env.PROJECT_RIG_ROOT = projectRoot;
1867
+ env.RIG_TASK_WORKSPACE = ctx.workspaceDir;
1868
+ env.MONOREPO_ROOT = ctx.workspaceDir;
1869
+ env.MONOREPO_MAIN_ROOT = resolveMonorepoRoot(projectRoot);
1870
+ } else if (projectRoot) {
1871
+ env.PROJECT_RIG_ROOT = projectRoot;
1872
+ }
1873
+ if (runtimeRoot) {
1874
+ env.RIG_RUNTIME_HOME = runtimeRoot;
1875
+ }
1876
+ if (runtimeHome && existsSync3(runtimeHome)) {
1877
+ env.HOME = runtimeHome;
1878
+ env.OPENSSL_CONF = ensureRuntimeOpenSslConfig(runtimeHome);
1879
+ }
1880
+ if (runtimeTmp && existsSync3(runtimeTmp)) {
1881
+ env.TMPDIR = runtimeTmp;
1882
+ }
1883
+ if (runtimeCache && existsSync3(runtimeCache)) {
1884
+ env.XDG_CACHE_HOME = runtimeCache;
1885
+ }
1886
+ const workspaceSecrets = loadDotEnvSecrets(ctx?.workspaceDir || projectRoot, process.env);
1887
+ for (const [key, value] of Object.entries(resolveRuntimeSecrets(process.env, workspaceSecrets))) {
1888
+ if (key === "GITHUB_SSH_KEY" || !value) {
1889
+ continue;
1890
+ }
1891
+ env[key] = value;
1892
+ }
1893
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || authStateToken(process.env) || "";
1894
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
1895
+ env.GITHUB_TOKEN = rigGithubToken;
1896
+ }
1897
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
1898
+ env.GITHUB_TOKEN = env.GH_TOKEN;
1899
+ }
1900
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
1901
+ env.GH_TOKEN = env.GITHUB_TOKEN;
1902
+ }
1903
+ if (!env.GREPTILE_GITHUB_TOKEN && env.GITHUB_TOKEN) {
1904
+ env.GREPTILE_GITHUB_TOKEN = env.GITHUB_TOKEN;
1905
+ }
1906
+ const persistedSecrets = loadPersistedRuntimeSecrets(runtimeRoot);
1907
+ for (const [key, value] of Object.entries(persistedSecrets)) {
1908
+ if (!value)
1909
+ continue;
1910
+ if (!env[key]) {
1911
+ env[key] = value;
1912
+ }
1913
+ }
1914
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
1915
+ env.GITHUB_TOKEN = env.GH_TOKEN;
1916
+ }
1917
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
1918
+ env.GH_TOKEN = env.GITHUB_TOKEN;
1919
+ }
1920
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
1921
+ if (gitHubToken) {
1922
+ env.RIG_GITHUB_TOKEN = gitHubToken;
1923
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
1924
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
1925
+ applyGitHubCredentialHelperEnv(env);
1926
+ }
1927
+ if (runtimeKnownHosts && existsSync3(runtimeKnownHosts)) {
1928
+ const sshParts = [
1929
+ "ssh",
1930
+ `-o UserKnownHostsFile="${runtimeKnownHosts}"`,
1931
+ "-o StrictHostKeyChecking=yes",
1932
+ "-F /dev/null"
1933
+ ];
1934
+ if (runtimeKey && existsSync3(runtimeKey)) {
1935
+ sshParts.splice(1, 0, `-i "${runtimeKey}"`, "-o IdentitiesOnly=yes");
1936
+ }
1937
+ env.GIT_SSH_COMMAND = sshParts.join(" ");
1938
+ } else if (process.env.GIT_SSH_COMMAND?.trim()) {
1939
+ env.GIT_SSH_COMMAND = process.env.GIT_SSH_COMMAND;
1940
+ }
1941
+ return Object.keys(env).length > 0 ? env : undefined;
1942
+ }
1943
+ function applyGitHubCredentialHelperEnv(env) {
1944
+ env.GIT_TERMINAL_PROMPT = "0";
1945
+ env.GIT_CONFIG_COUNT = "2";
1946
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
1947
+ env.GIT_CONFIG_VALUE_0 = "";
1948
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
1949
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
1950
+ }
1951
+ function loadPersistedRuntimeSecrets(runtimeRoot) {
1952
+ if (!runtimeRoot) {
1953
+ return {};
1954
+ }
1955
+ const path = resolve3(runtimeRoot, "runtime-secrets.json");
1956
+ if (!existsSync3(path)) {
1957
+ return {};
1958
+ }
1959
+ try {
1960
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
1961
+ const allowed = new Set(["GITHUB_TOKEN", "GH_TOKEN", "RIG_GITHUB_TOKEN"]);
1962
+ const entries = Object.entries(parsed).filter((entry) => typeof entry[1] === "string" && allowed.has(entry[0]));
1963
+ return Object.fromEntries(entries);
1964
+ } catch {
1965
+ return {};
1966
+ }
1967
+ }
1968
+ function ensureRuntimeOpenSslConfig(runtimeHome) {
1969
+ const sslDir = resolve3(runtimeHome, ".ssl");
1970
+ const sslConfig = resolve3(sslDir, "openssl.cnf");
1971
+ if (!existsSync3(sslDir)) {
1972
+ mkdirSync(sslDir, { recursive: true });
1973
+ }
1974
+ if (!existsSync3(sslConfig)) {
1975
+ writeFileSync(sslConfig, `# Rig runtime OpenSSL config placeholder
1976
+ `);
1977
+ }
1978
+ return sslConfig;
1979
+ }
1980
+ function resolveRuntimeMetadata(projectRoot) {
1981
+ const contextFile = process.env.RIG_RUNTIME_CONTEXT_FILE?.trim();
1982
+ const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
1983
+ let ctx = loadRuntimeContextFromEnv();
1984
+ if (runtimeHome) {
1985
+ return {
1986
+ ctx,
1987
+ runtimeRoot: runtimeHome
1988
+ };
1989
+ }
1990
+ if (contextFile) {
1991
+ return {
1992
+ ctx,
1993
+ runtimeRoot: dirname(resolve3(contextFile))
1994
+ };
1995
+ }
1996
+ const inferredContextFile = findRuntimeContextFile(projectRoot);
1997
+ if (existsSync3(inferredContextFile)) {
1998
+ try {
1999
+ ctx = loadRuntimeContext(inferredContextFile);
2000
+ } catch {}
2001
+ return {
2002
+ ctx,
2003
+ runtimeRoot: dirname(inferredContextFile)
2004
+ };
2005
+ }
2006
+ return { ctx, runtimeRoot: "" };
2007
+ }
2008
+ function findRuntimeContextFile(startPath) {
2009
+ let current = resolve3(startPath);
2010
+ while (true) {
2011
+ const candidate = resolve3(current, "runtime-context.json");
2012
+ if (existsSync3(candidate)) {
2013
+ return candidate;
2014
+ }
2015
+ const parent = dirname(current);
2016
+ if (parent === current) {
2017
+ return "";
2018
+ }
2019
+ current = parent;
2020
+ }
2021
+ }
2022
+ var TASK_RUNTIME_STAGE_EXCLUDES, GENERATED_STAGE_EXCLUDES, TASK_ARTIFACT_STAGE_FALLBACK;
2023
+ var init_git_ops = __esm(() => {
2024
+ init_task_data();
2025
+ init_github_auth_env();
2026
+ init_host_git();
2027
+ TASK_RUNTIME_STAGE_EXCLUDES = [
2028
+ ".rig/bin/**",
2029
+ ".rig/cache/**",
2030
+ ".rig/home/**",
2031
+ ".rig/logs/**",
2032
+ ".rig/runtime/**",
2033
+ ".rig/session/**",
2034
+ ".rig/state/**",
2035
+ ".rig/runtime-context.json"
2036
+ ];
2037
+ GENERATED_STAGE_EXCLUDES = ["artifacts/*/runtime-snapshots/**"];
2038
+ TASK_ARTIFACT_STAGE_FALLBACK = new Set([
2039
+ "changed-files.txt",
2040
+ "contract-changes.md",
2041
+ "decision-log.md",
2042
+ "git-state.txt",
2043
+ "next-actions.md",
2044
+ "pr-state.json",
2045
+ "task-result.json",
2046
+ "validation-summary.json"
2047
+ ]);
2048
+ });
2049
+
2050
+ // packages/bundle-default-lifecycle/src/control-plane/policy.ts
2051
+ import { existsSync as existsSync4, readFileSync as readFileSync3, statSync } from "fs";
2052
+ import { resolve as resolve4 } from "path";
27
2053
  import {
28
- collectPrReviewEvidence,
29
- evaluateStrictPrMergeGate,
30
- parseGreptileScore,
31
- stripHtml
32
- } from "@rig/pr-review-plugin";
2054
+ POLICY_VERSION
2055
+ } from "@rig/contracts";
2056
+ function defaultPolicy() {
2057
+ return {
2058
+ version: POLICY_VERSION,
2059
+ mode: "enforce",
2060
+ scope: { ...DEFAULT_SCOPE },
2061
+ rules: [],
2062
+ sandbox: { ...DEFAULT_SANDBOX },
2063
+ isolation: { ...DEFAULT_ISOLATION },
2064
+ completion: { ...DEFAULT_COMPLETION },
2065
+ runtime_image: {
2066
+ deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
2067
+ plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
2068
+ },
2069
+ runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
2070
+ };
2071
+ }
2072
+ function seedPolicyFromContent(rawJson) {
2073
+ try {
2074
+ seededPolicyConfig = mergeWithDefaults(JSON.parse(rawJson));
2075
+ } catch {
2076
+ seededPolicyConfig = null;
2077
+ }
2078
+ }
2079
+ function loadPolicy(projectRoot) {
2080
+ if (seededPolicyConfig) {
2081
+ return seededPolicyConfig;
2082
+ }
2083
+ const configPath = resolve4(projectRoot, "rig/policy/policy.json");
2084
+ if (!existsSync4(configPath)) {
2085
+ return defaultPolicy();
2086
+ }
2087
+ let mtimeMs;
2088
+ try {
2089
+ mtimeMs = statSync(configPath).mtimeMs;
2090
+ } catch {
2091
+ return defaultPolicy();
2092
+ }
2093
+ if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
2094
+ return policyCache.config;
2095
+ }
2096
+ try {
2097
+ const config = mergeWithDefaults(JSON.parse(readFileSync3(configPath, "utf-8")));
2098
+ policyCache = { mtimeMs, config };
2099
+ policyCachePath = configPath;
2100
+ return config;
2101
+ } catch {
2102
+ return defaultPolicy();
2103
+ }
2104
+ }
2105
+ function mergeWithDefaults(parsed) {
2106
+ const base = defaultPolicy();
2107
+ if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
2108
+ base.mode = parsed.mode;
2109
+ }
2110
+ if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
2111
+ const scope = parsed.scope;
2112
+ if (typeof scope.fail_closed === "boolean")
2113
+ base.scope.fail_closed = scope.fail_closed;
2114
+ if (typeof scope.harness_paths_exempt === "boolean")
2115
+ base.scope.harness_paths_exempt = scope.harness_paths_exempt;
2116
+ if (typeof scope.runtime_paths_exempt === "boolean")
2117
+ base.scope.runtime_paths_exempt = scope.runtime_paths_exempt;
2118
+ }
2119
+ if (Array.isArray(parsed.rules)) {
2120
+ base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
2121
+ }
2122
+ if (Array.isArray(parsed.deny) && base.rules.length === 0) {
2123
+ base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
2124
+ }
2125
+ if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
2126
+ const sandbox = parsed.sandbox;
2127
+ if (typeof sandbox.mode === "string" && isValidMode(sandbox.mode))
2128
+ base.sandbox.mode = sandbox.mode;
2129
+ if (typeof sandbox.network === "boolean")
2130
+ base.sandbox.network = sandbox.network;
2131
+ if (Array.isArray(sandbox.read_deny))
2132
+ base.sandbox.read_deny = sandbox.read_deny.filter((value) => typeof value === "string");
2133
+ if (typeof sandbox.write_allow_from_runtime === "boolean")
2134
+ base.sandbox.write_allow_from_runtime = sandbox.write_allow_from_runtime;
2135
+ }
2136
+ if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
2137
+ const isolation = parsed.isolation;
2138
+ if (isolation.default_mode === "worktree")
2139
+ base.isolation.default_mode = isolation.default_mode;
2140
+ if (typeof isolation.repo_symlink_fallback === "boolean")
2141
+ base.isolation.repo_symlink_fallback = isolation.repo_symlink_fallback;
2142
+ if (typeof isolation.strict_provisioning === "boolean")
2143
+ base.isolation.strict_provisioning = isolation.strict_provisioning;
2144
+ if (typeof isolation.fail_closed_on_provision_error === "boolean")
2145
+ base.isolation.fail_closed_on_provision_error = isolation.fail_closed_on_provision_error;
2146
+ }
2147
+ if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
2148
+ const completion = parsed.completion;
2149
+ if (typeof completion.derive_checks_from_scope === "boolean")
2150
+ base.completion.derive_checks_from_scope = completion.derive_checks_from_scope;
2151
+ if (Array.isArray(completion.checks))
2152
+ base.completion.checks = completion.checks.filter((value) => typeof value === "string");
2153
+ if (Array.isArray(completion.typescript_config_probe))
2154
+ base.completion.typescript_config_probe = completion.typescript_config_probe.filter((value) => typeof value === "string");
2155
+ if (Array.isArray(completion.eslint_config_probe))
2156
+ base.completion.eslint_config_probe = completion.eslint_config_probe.filter((value) => typeof value === "string");
2157
+ }
2158
+ if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
2159
+ const runtimeImage = parsed.runtime_image;
2160
+ if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
2161
+ const deps = runtimeImage.deps;
2162
+ if (typeof deps.monorepo_install === "boolean")
2163
+ base.runtime_image.deps.monorepo_install = deps.monorepo_install;
2164
+ if (typeof deps.hp_next_install === "boolean")
2165
+ base.runtime_image.deps.hp_next_install = deps.hp_next_install;
2166
+ }
2167
+ if (typeof runtimeImage.plugins_require_binaries === "boolean") {
2168
+ base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
2169
+ }
2170
+ }
2171
+ if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
2172
+ const runtimeSnapshot = parsed.runtime_snapshot;
2173
+ if (typeof runtimeSnapshot.enabled === "boolean")
2174
+ base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
2175
+ }
2176
+ return base;
2177
+ }
2178
+ function isValidMode(value) {
2179
+ return value === "off" || value === "observe" || value === "enforce";
2180
+ }
2181
+ function isValidRule(value) {
2182
+ if (!value || typeof value !== "object" || Array.isArray(value))
2183
+ return false;
2184
+ const rule = value;
2185
+ return typeof rule.id === "string" && typeof rule.category === "string" && !!rule.match && typeof rule.match === "object";
2186
+ }
2187
+ function migrateLegacyDeny(deny) {
2188
+ const rules = [];
2189
+ for (const entry of deny) {
2190
+ if (typeof entry.id !== "string")
2191
+ continue;
2192
+ const match = {};
2193
+ if (typeof entry.pattern === "string")
2194
+ match.pattern = entry.pattern;
2195
+ if (typeof entry.regex === "string")
2196
+ match.regex = entry.regex;
2197
+ if (!match.pattern && !match.regex)
2198
+ continue;
2199
+ const rule = {
2200
+ id: entry.id,
2201
+ category: "command",
2202
+ match,
2203
+ action: entry.action === "warn" ? "warn" : "block"
2204
+ };
2205
+ if (typeof entry.reason === "string") {
2206
+ rule.description = entry.reason;
2207
+ }
2208
+ rules.push(rule);
2209
+ }
2210
+ return rules;
2211
+ }
2212
+ function precompilePolicyRuleRegexes(rules) {
2213
+ return rules.map((rule) => {
2214
+ const compiled = { ...rule };
2215
+ const matchRegex = compileRegex(rule.match?.regex);
2216
+ const unlessRegex = compileRegex(rule.unless?.regex);
2217
+ if (matchRegex) {
2218
+ compiled.compiledRegex = matchRegex;
2219
+ }
2220
+ if (unlessRegex) {
2221
+ compiled.compiledUnlessRegex = unlessRegex;
2222
+ }
2223
+ return compiled;
2224
+ });
2225
+ }
2226
+ function compileRegex(pattern) {
2227
+ if (!pattern)
2228
+ return;
2229
+ try {
2230
+ return new RegExp(pattern);
2231
+ } catch {
2232
+ return;
2233
+ }
2234
+ }
2235
+ var DEFAULT_SCOPE, DEFAULT_SANDBOX, DEFAULT_ISOLATION, DEFAULT_COMPLETION, DEFAULT_RUNTIME_IMAGE, DEFAULT_RUNTIME_SNAPSHOT, policyCache = null, policyCachePath = null, seededPolicyConfig = null;
2236
+ var init_policy = __esm(() => {
2237
+ DEFAULT_SCOPE = {
2238
+ fail_closed: true,
2239
+ harness_paths_exempt: true,
2240
+ runtime_paths_exempt: true
2241
+ };
2242
+ DEFAULT_SANDBOX = {
2243
+ mode: "enforce",
2244
+ network: true,
2245
+ read_deny: [],
2246
+ write_allow_from_runtime: true
2247
+ };
2248
+ DEFAULT_ISOLATION = {
2249
+ default_mode: "worktree",
2250
+ repo_symlink_fallback: false,
2251
+ strict_provisioning: true,
2252
+ fail_closed_on_provision_error: true
2253
+ };
2254
+ DEFAULT_COMPLETION = {
2255
+ derive_checks_from_scope: true,
2256
+ checks: [],
2257
+ typescript_config_probe: ["tsconfig.json"],
2258
+ eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
2259
+ };
2260
+ DEFAULT_RUNTIME_IMAGE = {
2261
+ deps: {
2262
+ monorepo_install: false,
2263
+ hp_next_install: false
2264
+ },
2265
+ plugins_require_binaries: true
2266
+ };
2267
+ DEFAULT_RUNTIME_SNAPSHOT = {
2268
+ enabled: true
2269
+ };
2270
+ });
2271
+
2272
+ // packages/bundle-default-lifecycle/src/control-plane/verifier.ts
2273
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2274
+ import { resolve as resolve5 } from "path";
2275
+ import { resolveRuntimeSecrets as resolveRuntimeSecrets2 } from "@rig/core/baked-secrets";
2276
+ import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/core/runtime-context";
2277
+ import { nowIso as nowIso2, runCapture as runCapture2 } from "@rig/core/exec";
2278
+ import { resolveHarnessPaths } from "@rig/core/harness-paths";
2279
+ async function ensureMergeGate(projectRoot) {
2280
+ mergeGateHolder = await resolvePrMergeGateService(projectRoot);
2281
+ return mergeGateHolder;
2282
+ }
2283
+ function mg() {
2284
+ if (!mergeGateHolder) {
2285
+ throw new Error("PR merge-gate capability not resolved (verifyTask must run first).");
2286
+ }
2287
+ return mergeGateHolder;
2288
+ }
33
2289
  async function verifyTask(options) {
2290
+ await ensureMergeGate(options.projectRoot);
34
2291
  const paths = resolveHarnessPaths(options.projectRoot);
35
2292
  const taskId = options.taskId;
36
- const normalizedTaskId = lookupTask(options.projectRoot, taskId);
37
- const artifactDir = artifactDirForId(options.projectRoot, taskId);
38
- mkdirSync(artifactDir, { recursive: true });
39
- const validationSummaryPath = resolve(artifactDir, "validation-summary.json");
40
- const reviewFeedbackPath = resolve(artifactDir, "review-feedback.md");
41
- const reviewStatePath = resolve(artifactDir, "review-state.json");
42
- const greptileRawPath = resolve(artifactDir, "review-greptile-raw.json");
2293
+ const normalizedTaskId = taskData().lookupTask(options.projectRoot, taskId);
2294
+ const artifactDir = taskData().artifactDirForId(options.projectRoot, taskId);
2295
+ mkdirSync2(artifactDir, { recursive: true });
2296
+ const validationSummaryPath = resolve5(artifactDir, "validation-summary.json");
2297
+ const reviewFeedbackPath = resolve5(artifactDir, "review-feedback.md");
2298
+ const reviewStatePath = resolve5(artifactDir, "review-state.json");
2299
+ const greptileRawPath = resolve5(artifactDir, "review-greptile-raw.json");
43
2300
  const prStates = readPrMetadata(options.projectRoot, taskId);
44
2301
  const prState = prStates[0] || null;
45
2302
  const localReasons = [];
@@ -51,7 +2308,7 @@ async function verifyTask(options) {
51
2308
  if (!normalizedTaskId && !await hasConfiguredSourceTask(options.projectRoot, taskId)) {
52
2309
  localReasons.push(`[Task Config] Unknown task id '${taskId}' in task-config or configured task source.`);
53
2310
  }
54
- if (!existsSync(validationSummaryPath)) {
2311
+ if (!existsSync5(validationSummaryPath)) {
55
2312
  localReasons.push(`[Artifact Quality] validation-summary.json not found at ${validationSummaryPath}.`);
56
2313
  } else {
57
2314
  const summary = await parseValidationSummary(validationSummaryPath);
@@ -60,13 +2317,13 @@ async function verifyTask(options) {
60
2317
  }
61
2318
  }
62
2319
  for (const file of ["task-result.json", "decision-log.md", "next-actions.md", "changed-files.txt"]) {
63
- const requiredPath = resolve(artifactDir, file);
64
- if (!existsSync(requiredPath)) {
2320
+ const requiredPath = resolve5(artifactDir, file);
2321
+ if (!existsSync5(requiredPath)) {
65
2322
  localReasons.push(`[Artifact Quality] Missing required artifact file: ${requiredPath}`);
66
2323
  }
67
2324
  }
68
- const taskResultPath = resolve(artifactDir, "task-result.json");
69
- if (existsSync(taskResultPath)) {
2325
+ const taskResultPath = resolve5(artifactDir, "task-result.json");
2326
+ if (existsSync5(taskResultPath)) {
70
2327
  const taskResult = await readJsonFile(taskResultPath);
71
2328
  const artifactStatus = typeof taskResult?.status === "string" ? taskResult.status.trim().toLowerCase() : "";
72
2329
  if (artifactStatus === "partial") {
@@ -79,8 +2336,8 @@ async function verifyTask(options) {
79
2336
  localReasons.push("[Artifact Quality] task-result.json next actions indicate remaining implementation scope.");
80
2337
  }
81
2338
  }
82
- const nextActionsPath = resolve(artifactDir, "next-actions.md");
83
- if (existsSync(nextActionsPath)) {
2339
+ const nextActionsPath = resolve5(artifactDir, "next-actions.md");
2340
+ if (existsSync5(nextActionsPath)) {
84
2341
  const nextActionsContent = await Bun.file(nextActionsPath).text();
85
2342
  if (nextActionsContent.includes("TODO: Replace this scaffold") || nextActionsContent.includes("bd-<downstream-task-id>")) {
86
2343
  localReasons.push("[Artifact Quality] next-actions.md still contains scaffold placeholder text. Replace with real recommendations.");
@@ -111,7 +2368,7 @@ async function verifyTask(options) {
111
2368
  aiReasons.push(`[AI Review] Required mode needs a completed Greptile approval; current verdict is ${ai.verdict}.`);
112
2369
  }
113
2370
  if (persistArtifacts && ai.rawResponse) {
114
- writeFileSync(greptileRawPath, `${ai.rawResponse}
2371
+ writeFileSync2(greptileRawPath, `${ai.rawResponse}
115
2372
  `, "utf-8");
116
2373
  }
117
2374
  } else if (!options.skipAiReview && reviewMode === "off") {
@@ -233,15 +2490,15 @@ function nextActionsIndicateRemainingScope(content) {
233
2490
  return /^\s*- \[ \]/m.test(normalized) || /\b(remaining scope|still need|needs? to be implemented|not yet implemented|not implemented|follow[- ]?up required|blocked by|blocker:)\b/i.test(lower);
234
2491
  }
235
2492
  async function hasConfiguredSourceTask(projectRoot, taskId) {
236
- return readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task !== null).catch(() => false);
2493
+ return taskData().readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task !== null).catch(() => false);
237
2494
  }
238
2495
  function resolveGithubSourceIssueId(projectRoot, taskId) {
239
- const fromRuntime = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
2496
+ const fromRuntime = loadRuntimeContextFromEnv2()?.sourceTask?.sourceIssueId;
240
2497
  if (typeof fromRuntime === "string" && isGithubSourceIssueId(fromRuntime)) {
241
2498
  return fromRuntime;
242
2499
  }
243
2500
  try {
244
- const taskConfig = readTaskConfig(projectRoot);
2501
+ const taskConfig = taskData().readTaskConfig(projectRoot);
245
2502
  const entry = taskConfig[taskId];
246
2503
  const sourceIssueId = typeof entry?.sourceIssueId === "string" ? entry.sourceIssueId : typeof entry?.source_issue_id === "string" ? entry.source_issue_id : null;
247
2504
  if (sourceIssueId && isGithubSourceIssueId(sourceIssueId)) {
@@ -312,14 +2569,15 @@ function loadGithubPullRequestCloseoutSnapshot(projectRoot, prState) {
312
2569
  "--json",
313
2570
  "state,isDraft,mergeable,mergeStateStatus,reviewDecision,title,body,statusCheckRollup"
314
2571
  ]);
2572
+ const isDraft = booleanField(view, "isDraft");
315
2573
  return {
316
- state: stringField(view, "state"),
317
- isDraft: booleanField(view, "isDraft"),
318
- mergeable: stringField(view, "mergeable"),
319
- mergeStateStatus: stringField(view, "mergeStateStatus"),
320
- reviewDecision: stringField(view, "reviewDecision"),
321
- title: stringField(view, "title"),
322
- body: stringField(view, "body"),
2574
+ ...objectField("state", stringField(view, "state")),
2575
+ ...isDraft !== undefined ? { isDraft } : {},
2576
+ ...objectField("mergeable", stringField(view, "mergeable")),
2577
+ ...objectField("mergeStateStatus", stringField(view, "mergeStateStatus")),
2578
+ ...objectField("reviewDecision", stringField(view, "reviewDecision")),
2579
+ ...objectField("title", stringField(view, "title")),
2580
+ ...objectField("body", stringField(view, "body")),
323
2581
  statusCheckRollup: statusCheckRollupField(view, "statusCheckRollup"),
324
2582
  reviewThreads: loadGithubReviewThreads(projectRoot, repoName, prNumber)
325
2583
  };
@@ -394,6 +2652,9 @@ function stringField(record, key) {
394
2652
  const value = record[key];
395
2653
  return typeof value === "string" ? value : undefined;
396
2654
  }
2655
+ function objectField(key, value) {
2656
+ return value === undefined ? {} : { [key]: value };
2657
+ }
397
2658
  function booleanField(record, key) {
398
2659
  const value = record[key];
399
2660
  return typeof value === "boolean" ? value : undefined;
@@ -450,7 +2711,7 @@ function isAcceptedValidationSummary(summary) {
450
2711
  return summary.status === "skipped" && summary.total === 0 && summary.failed === 0;
451
2712
  }
452
2713
  async function loadReviewMode(reviewProfilePath, fallback) {
453
- const parsed = existsSync(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
2714
+ const parsed = existsSync5(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
454
2715
  const mode = parsed?.mode;
455
2716
  if (mode === "off" || mode === "advisory" || mode === "required") {
456
2717
  return mode;
@@ -461,7 +2722,7 @@ async function loadReviewMode(reviewProfilePath, fallback) {
461
2722
  return "advisory";
462
2723
  }
463
2724
  async function loadReviewProvider(reviewProfilePath, fallback) {
464
- const parsed = existsSync(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
2725
+ const parsed = existsSync5(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
465
2726
  const provider = parsed?.provider;
466
2727
  if (typeof provider === "string" && provider.trim().length > 0) {
467
2728
  return provider;
@@ -470,7 +2731,7 @@ async function loadReviewProvider(reviewProfilePath, fallback) {
470
2731
  }
471
2732
  function resolveRepoSlug(projectRoot) {
472
2733
  const paths = resolveHarnessPaths(projectRoot);
473
- const remote = runCapture(["git", "-C", paths.monorepoRoot, "remote", "get-url", "origin"], projectRoot).stdout.trim() || runCapture(["git", "-C", projectRoot, "remote", "get-url", "origin"], projectRoot).stdout.trim();
2734
+ const remote = runCapture2(["git", "-C", paths.monorepoRoot, "remote", "get-url", "origin"], projectRoot).stdout.trim() || runCapture2(["git", "-C", projectRoot, "remote", "get-url", "origin"], projectRoot).stdout.trim();
474
2735
  if (!remote) {
475
2736
  return "";
476
2737
  }
@@ -484,7 +2745,7 @@ function resolveRepoSlug(projectRoot) {
484
2745
  async function runGreptileReview(options) {
485
2746
  const reasons = [];
486
2747
  const warnings = [];
487
- const secrets = resolveRuntimeSecrets(process.env);
2748
+ const secrets = resolveRuntimeSecrets2(process.env);
488
2749
  const apiKey = secrets.GREPTILE_API_KEY || "";
489
2750
  const apiBase = secrets.GREPTILE_API_BASE || "https://api.greptile.com/mcp";
490
2751
  const remote = secrets.GREPTILE_REMOTE || "github";
@@ -620,7 +2881,7 @@ function writeFeedbackFile(options) {
620
2881
  if (options.aiRawFeedback) {
621
2882
  lines.push("## Raw Reviewer Feedback", "", "```text", options.aiRawFeedback, "```", "");
622
2883
  }
623
- writeFileSync(options.output, `${lines.join(`
2884
+ writeFileSync2(options.output, `${lines.join(`
624
2885
  `)}
625
2886
  `, "utf-8");
626
2887
  }
@@ -635,9 +2896,9 @@ function writeReviewStateFile(options) {
635
2896
  local_reasons: options.localReasons,
636
2897
  ai_reasons: options.aiReasons,
637
2898
  ai_warnings: options.aiWarnings,
638
- updated_at: nowIso()
2899
+ updated_at: nowIso2()
639
2900
  };
640
- writeFileSync(options.output, `${JSON.stringify(payload, null, 2)}
2901
+ writeFileSync2(options.output, `${JSON.stringify(payload, null, 2)}
641
2902
  `, "utf-8");
642
2903
  }
643
2904
  async function runGreptileReviewForPr(options) {
@@ -662,7 +2923,6 @@ async function runGreptileReviewForPr(options) {
662
2923
  taskId: options.taskId,
663
2924
  prState: options.prState,
664
2925
  reviewMode: options.reviewMode,
665
- infrastructureError: undefined,
666
2926
  pollAttempts: options.pollAttempts,
667
2927
  pollIntervalMs: options.pollIntervalMs
668
2928
  });
@@ -790,7 +3050,7 @@ async function runGreptileReviewForPr(options) {
790
3050
  });
791
3051
  const actionableComments = filterActionableGreptileComments(commentsPayload.comments || []);
792
3052
  const reviewBody = reviewDetails.codeReview?.body || "";
793
- const score = parseGreptileScore(reviewBody);
3053
+ const score = mg().parseGreptileScore(reviewBody);
794
3054
  const feedback = [
795
3055
  `## ${options.prState.repoLabel || repoName} PR Review`,
796
3056
  "",
@@ -798,7 +3058,7 @@ async function runGreptileReviewForPr(options) {
798
3058
  `- Review ID: ${selectedReview.id}`,
799
3059
  `- Status: ${selectedReview.status}`,
800
3060
  "",
801
- reviewBody ? stripHtml(reviewBody).trim() : "Greptile completed without summary body."
3061
+ reviewBody ? mg().stripHtml(reviewBody).trim() : "Greptile completed without summary body."
802
3062
  ].filter(Boolean).join(`
803
3063
  `);
804
3064
  if (actionableComments.length > 0) {
@@ -819,7 +3079,7 @@ async function runGreptileReviewForPr(options) {
819
3079
  }
820
3080
  };
821
3081
  }
822
- const blockerScanBody = stripHtml(reviewBody).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
3082
+ const blockerScanBody = mg().stripHtml(reviewBody).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
823
3083
  if (/not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this|\breject(?:ed|ion)?\b|\bskip(?:ped)?\b|status\s*:\s*(?:reject(?:ed)?|skip(?:ped)?|failed)/i.test(blockerScanBody)) {
824
3084
  reasons.push(`[AI Review] ${repoName}#${prNumber} summary indicates the PR is not safe to merge.`);
825
3085
  return {
@@ -867,7 +3127,7 @@ async function runGreptileReviewForPr(options) {
867
3127
  status: selectedReview.status
868
3128
  }]
869
3129
  });
870
- strictGate = evaluateStrictPrMergeGate(strictEvidence);
3130
+ strictGate = mg().evaluateGate(strictEvidence);
871
3131
  } catch (error) {
872
3132
  reasons.push(`[AI Review] Strict Greptile evidence collection failed for ${repoName}#${prNumber}: ${error instanceof Error ? error.message : String(error)}`);
873
3133
  return {
@@ -994,7 +3254,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
994
3254
  fallbackReview?.html_url ? `- Review: ${fallbackReview.html_url}` : "",
995
3255
  fallbackReview?.state ? `- Status: ${fallbackReview.state}` : "",
996
3256
  "",
997
- fallbackReview?.body?.trim() ? stripHtml(fallbackReview.body).trim() : "Greptile MCP was unavailable, so verification used GitHub review threads instead."
3257
+ fallbackReview?.body?.trim() ? mg().stripHtml(fallbackReview.body).trim() : "Greptile MCP was unavailable, so verification used GitHub review threads instead."
998
3258
  ].filter(Boolean).join(`
999
3259
  `);
1000
3260
  const warnings = buildGithubGreptileFallbackWarnings(options);
@@ -1017,7 +3277,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
1017
3277
  taskId: options.taskId,
1018
3278
  prUrl
1019
3279
  });
1020
- strictGate = evaluateStrictPrMergeGate(strictEvidence);
3280
+ strictGate = mg().evaluateGate(strictEvidence);
1021
3281
  } catch (error) {
1022
3282
  return {
1023
3283
  verdict: "REJECT",
@@ -1242,7 +3502,7 @@ function loadGithubPullRequestState(projectRoot, repoName, prNumber) {
1242
3502
  ]);
1243
3503
  return {
1244
3504
  state: response.state || "",
1245
- merged: response.merged,
3505
+ ...response.merged !== undefined ? { merged: response.merged } : {},
1246
3506
  merged_at: response.merged_at ?? null
1247
3507
  };
1248
3508
  }
@@ -1260,7 +3520,7 @@ function parsePullRequestNumber(url) {
1260
3520
  return match ? Number.parseInt(match[1] || "0", 10) : 0;
1261
3521
  }
1262
3522
  function runGhJson(projectRoot, args) {
1263
- const result = runCapture(["gh", ...args], projectRoot);
3523
+ const result = runCapture2(["gh", ...args], projectRoot);
1264
3524
  if (result.exitCode !== 0) {
1265
3525
  throw new Error(result.stderr || result.stdout || `gh ${args.join(" ")} failed`);
1266
3526
  }
@@ -1271,7 +3531,7 @@ function runGhJson(projectRoot, args) {
1271
3531
  }
1272
3532
  }
1273
3533
  async function collectStrictPrEvidenceForVerifier(input) {
1274
- return collectPrReviewEvidence({
3534
+ return mg().collectEvidence({
1275
3535
  projectRoot: input.projectRoot,
1276
3536
  prUrl: input.prUrl,
1277
3537
  taskId: input.taskId,
@@ -1279,7 +3539,7 @@ async function collectStrictPrEvidenceForVerifier(input) {
1279
3539
  cycle: 0,
1280
3540
  apiSignals: input.apiSignals ?? [],
1281
3541
  command: async (args, options) => {
1282
- const result = runCapture(["gh", ...args], options?.cwd ?? input.projectRoot);
3542
+ const result = runCapture2(["gh", ...args], options?.cwd ?? input.projectRoot);
1283
3543
  return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
1284
3544
  }
1285
3545
  });
@@ -1292,11 +3552,11 @@ function deriveRepoName(projectRoot, prState) {
1292
3552
  if (prState.target === "monorepo") {
1293
3553
  return resolveRepoSlug(projectRoot);
1294
3554
  }
1295
- return runCapture(["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"], projectRoot).stdout.trim();
3555
+ return runCapture2(["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"], projectRoot).stdout.trim();
1296
3556
  }
1297
3557
  function resolvePrHeadSha(projectRoot, prState) {
1298
3558
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
1299
- return runCapture(["git", "-C", repoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
3559
+ return runCapture2(["git", "-C", repoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
1300
3560
  }
1301
3561
  function isGreptileGithubLogin(login) {
1302
3562
  const normalized = (login || "").toLowerCase().replace(/\[bot\]$/, "");
@@ -1332,7 +3592,7 @@ function loadGithubPullRequestCheckRollup(projectRoot, repoName, prNumber) {
1332
3592
  return response.statusCheckRollup || [];
1333
3593
  }
1334
3594
  function evaluatePullRequestCiChecks(checks, repoName, prNumber, options = {}) {
1335
- const isPendingCheck = (check) => {
3595
+ const isPendingCheck2 = (check) => {
1336
3596
  if ((check.__typename || "") === "CheckRun") {
1337
3597
  return (check.status || "").toUpperCase() !== "COMPLETED";
1338
3598
  }
@@ -1341,7 +3601,7 @@ function evaluatePullRequestCiChecks(checks, repoName, prNumber, options = {}) {
1341
3601
  };
1342
3602
  const pendingGreptile = checks.filter((check) => {
1343
3603
  const label = (check.name || check.context || "").toLowerCase();
1344
- return label.includes("greptile") && isPendingCheck(check);
3604
+ return label.includes("greptile") && isPendingCheck2(check);
1345
3605
  });
1346
3606
  if (pendingGreptile.length > 0) {
1347
3607
  return {
@@ -1353,7 +3613,7 @@ function evaluatePullRequestCiChecks(checks, repoName, prNumber, options = {}) {
1353
3613
  const label = (check.name || check.context || "").toLowerCase();
1354
3614
  return label.length > 0 && !label.includes("greptile");
1355
3615
  });
1356
- const pending = nonGreptileChecks.filter(isPendingCheck);
3616
+ const pending = nonGreptileChecks.filter(isPendingCheck2);
1357
3617
  const mergeClean = (options.mergeStateStatus || "").toUpperCase() === "CLEAN";
1358
3618
  if (pending.length > 0 && !mergeClean) {
1359
3619
  return {
@@ -1436,7 +3696,7 @@ function filterActionableGithubGreptileThreads(threads) {
1436
3696
  }
1437
3697
  function resolvePrRepoRoot(projectRoot, prState) {
1438
3698
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
1439
- if (prState.target === "monorepo" && runtimeWorkspace && existsSync(resolve(runtimeWorkspace, ".git"))) {
3699
+ if (prState.target === "monorepo" && runtimeWorkspace && existsSync5(resolve5(runtimeWorkspace, ".git"))) {
1440
3700
  return runtimeWorkspace;
1441
3701
  }
1442
3702
  const paths = resolveHarnessPaths(projectRoot);
@@ -1447,10 +3707,10 @@ function isCommitAncestorOfPrHead(projectRoot, prState, reviewedCommit, headComm
1447
3707
  return false;
1448
3708
  }
1449
3709
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
1450
- return runCapture(["git", "-C", repoRoot, "merge-base", "--is-ancestor", reviewedCommit, headCommit], projectRoot).exitCode === 0;
3710
+ return runCapture2(["git", "-C", repoRoot, "merge-base", "--is-ancestor", reviewedCommit, headCommit], projectRoot).exitCode === 0;
1451
3711
  }
1452
3712
  function summarizeComment(input) {
1453
- const text = stripHtml(input).replace(/\s+/g, " ").trim();
3713
+ const text = mg().stripHtml(input).replace(/\s+/g, " ").trim();
1454
3714
  return text.length > 160 ? `${text.slice(0, 157)}...` : text;
1455
3715
  }
1456
3716
  function asGreptileInfrastructureWarning(reason) {
@@ -1465,7 +3725,12 @@ function isAiReviewApproved(input) {
1465
3725
  }
1466
3726
  return input.aiVerdict === "APPROVE" && input.aiReasons.length === 0;
1467
3727
  }
1468
- var init_verifier = () => {};
3728
+ var mergeGateHolder = null;
3729
+ var init_verifier = __esm(() => {
3730
+ init_git_ops();
3731
+ init_task_data();
3732
+ init_pr_merge_gate_cap();
3733
+ });
1469
3734
 
1470
3735
  // packages/bundle-default-lifecycle/src/control-plane/completion-verification.ts
1471
3736
  var exports_completion_verification = {};
@@ -1474,43 +3739,33 @@ __export(exports_completion_verification, {
1474
3739
  formatCompletionBlockedMessage: () => formatCompletionBlockedMessage,
1475
3740
  closeCompletedTaskSource: () => closeCompletedTaskSource
1476
3741
  });
1477
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1478
- import { resolve as resolve2 } from "path";
1479
- import { safePathSegment } from "@rig/shared/safe-identifiers";
3742
+ import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
3743
+ import { resolve as resolve6 } from "path";
3744
+ import { safePathSegment as safePathSegment2 } from "@rig/core/safe-identifiers";
1480
3745
  import {
1481
- escapeRegExp,
3746
+ escapeRegExp as escapeRegExp2,
1482
3747
  resolveBunCli,
1483
3748
  resolveBunCliInvocation,
1484
3749
  resolveTaskScopes,
1485
3750
  resolvePolicyContent
1486
3751
  } from "@rig/hook-kit";
1487
- import { loadPolicy, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
1488
- import { gitCommit, gitMergePr, gitOpenPr, readPrMetadata as readPrMetadata2, resolveTaskBranchRef } from "@rig/runtime/control-plane/native/git-ops";
1489
- import { runStrictPrMergeGate } from "@rig/pr-review-plugin";
1490
- import { strictMergeHeadShaFromGate } from "@rig/contracts";
1491
- import { changedFilesForTask, pendingFilesForTask, taskArtifacts, taskValidate } from "@rig/runtime/control-plane/native/task-ops";
1492
- import { currentTaskId } from "@rig/runtime/control-plane/native/task-state";
1493
- import { resolveHarnessPaths as resolveHarnessPaths2, runCapture as runCapture2 } from "@rig/runtime/control-plane/native/utils";
1494
- import { readSourceAwareTaskStatus } from "@rig/runtime/control-plane/tasks/source-aware-task-config-source";
1495
- import {
1496
- buildTaskRunLifecycleComment,
1497
- updateConfiguredTaskSourceTask
1498
- } from "@rig/runtime/control-plane/tasks/source-lifecycle";
1499
- import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
3752
+ import { runCapture as runCapture3 } from "@rig/core/exec";
3753
+ import { resolveHarnessPaths as resolveHarnessPaths2 } from "@rig/core/harness-paths";
3754
+ import { buildPluginHostContext as buildPluginHostContext2 } from "@rig/core/plugin-host-context";
1500
3755
  async function closeCompletedTaskSource(projectRoot, taskId) {
1501
- const comment = buildTaskRunLifecycleComment({
3756
+ const comment = taskData().buildTaskRunLifecycleComment({
1502
3757
  runId: process.env.RIG_SERVER_RUN_ID || taskId,
1503
3758
  status: "closed",
1504
3759
  summary: "Rig completion verification approved and closed this task.",
1505
- runtimeWorkspace: process.env.RIG_TASK_WORKSPACE,
1506
- logsDir: process.env.RIG_LOGS_DIR,
1507
- sessionDir: process.env.RIG_SESSION_FILE
3760
+ ...process.env.RIG_TASK_WORKSPACE !== undefined ? { runtimeWorkspace: process.env.RIG_TASK_WORKSPACE } : {},
3761
+ ...process.env.RIG_LOGS_DIR !== undefined ? { logsDir: process.env.RIG_LOGS_DIR } : {},
3762
+ ...process.env.RIG_SESSION_FILE !== undefined ? { sessionDir: process.env.RIG_SESSION_FILE } : {}
1508
3763
  });
1509
- const result = await updateConfiguredTaskSourceTask(projectRoot, {
3764
+ const result = await taskData().updateConfiguredTaskSourceTask(projectRoot, {
1510
3765
  taskId,
1511
3766
  update: { status: "closed", comment }
1512
3767
  });
1513
- const status = result.status ?? await readSourceAwareTaskStatus(projectRoot, result.taskId);
3768
+ const status = result.status ?? await taskData().readSourceAwareTaskStatus(projectRoot, result.taskId);
1514
3769
  if (!result.updated && status == null) {
1515
3770
  return {
1516
3771
  ok: true,
@@ -1530,7 +3785,7 @@ function isClosedStatus(status) {
1530
3785
  }
1531
3786
  async function runCompletionVerificationGate(projectRoot) {
1532
3787
  seedPolicyFromContent(resolvePolicyContent(projectRoot));
1533
- const taskId = currentTaskId(projectRoot);
3788
+ const taskId = taskData().currentTaskId(projectRoot);
1534
3789
  if (!taskId) {
1535
3790
  return { ok: true };
1536
3791
  }
@@ -1539,7 +3794,7 @@ async function runCompletionVerificationGate(projectRoot) {
1539
3794
  let sourceCloseoutAllowed = false;
1540
3795
  console.log(`=== Completion Verification: ${taskId} ===`);
1541
3796
  const scopes = await resolveTaskScopes(projectRoot, taskId);
1542
- const taskChangedFiles = changedFilesForTask(projectRoot, taskId, true);
3797
+ const taskChangedFiles = taskData().changedFilesForTask(projectRoot, taskId, true);
1543
3798
  const sourceInArtifacts = taskChangedFiles.filter((file) => /^artifacts\//.test(file) && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file));
1544
3799
  if (sourceInArtifacts.length > 0) {
1545
3800
  console.log(`
@@ -1552,13 +3807,13 @@ async function runCompletionVerificationGate(projectRoot) {
1552
3807
  }
1553
3808
  failed = true;
1554
3809
  }
1555
- const pluginHostCtx = await buildPluginHostContext(projectRoot).catch((error) => {
3810
+ const pluginHostCtx = await buildPluginHostContext2(projectRoot).catch((error) => {
1556
3811
  console.warn(`[completion-verification] plugin host unavailable for validators: ${error instanceof Error ? error.message : String(error)}`);
1557
3812
  return null;
1558
3813
  });
1559
3814
  console.log(`
1560
3815
  [1/3] Task validation...`);
1561
- if (!await taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined)) {
3816
+ if (!await taskData().taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined)) {
1562
3817
  console.log(`FAIL: Validation failed for ${taskId}`);
1563
3818
  failed = true;
1564
3819
  } else {
@@ -1571,7 +3826,7 @@ async function runCompletionVerificationGate(projectRoot) {
1571
3826
  failed = true;
1572
3827
  }
1573
3828
  }
1574
- taskArtifacts(projectRoot, taskId);
3829
+ taskData().taskArtifacts(projectRoot, taskId);
1575
3830
  const policy = loadPolicy(projectRoot);
1576
3831
  const openPrEnabled = policy.completion.checks.includes("open-pr");
1577
3832
  const autoMergeEnabled = policy.completion.checks.includes("auto-merge");
@@ -1598,7 +3853,7 @@ async function runCompletionVerificationGate(projectRoot) {
1598
3853
  } else {
1599
3854
  console.log("Verifier preflight: skipped (earlier checks failed)");
1600
3855
  }
1601
- const pendingTaskChangedFiles = pendingFilesForTask(projectRoot, taskId, true);
3856
+ const pendingTaskChangedFiles = taskData().pendingFilesForTask(projectRoot, taskId, true);
1602
3857
  const hasLocalChanges = pendingTaskChangedFiles.length > 0;
1603
3858
  console.log(`
1604
3859
  [post] Auto-committing task changes...`);
@@ -1671,14 +3926,15 @@ async function runCompletionVerificationGate(projectRoot) {
1671
3926
  console.log(`
1672
3927
  [post] Auto-merge...`);
1673
3928
  try {
1674
- const prs = readPrMetadata2(projectRoot, taskId);
3929
+ const prs = readPrMetadata(projectRoot, taskId);
1675
3930
  if (prs.length === 0) {
1676
3931
  console.log("Auto-merge: skipped (no PR metadata found)");
1677
3932
  } else {
3933
+ const mergeGate = await resolvePrMergeGateService(projectRoot);
1678
3934
  let cycle = 0;
1679
3935
  for (const pr of prs) {
1680
3936
  cycle += 1;
1681
- const gate = await runStrictPrMergeGate({
3937
+ const gate = await mergeGate.runGate({
1682
3938
  projectRoot,
1683
3939
  prUrl: pr.url,
1684
3940
  taskId,
@@ -1686,7 +3942,7 @@ async function runCompletionVerificationGate(projectRoot) {
1686
3942
  cycle,
1687
3943
  final: true,
1688
3944
  command: async (args, options) => {
1689
- const result = runCapture2(["gh", ...args], options?.cwd ?? projectRoot);
3945
+ const result = runCapture3(["gh", ...args], options?.cwd ?? projectRoot);
1690
3946
  return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
1691
3947
  }
1692
3948
  });
@@ -1703,7 +3959,7 @@ async function runCompletionVerificationGate(projectRoot) {
1703
3959
  pr,
1704
3960
  method: "squash",
1705
3961
  deleteBranch: true,
1706
- matchHeadCommit: strictMergeHeadShaFromGate(gate, pr.url)
3962
+ matchHeadCommit: mergeGate.resolveHeadSha({ result: gate, prUrl: pr.url })
1707
3963
  });
1708
3964
  if (mergeResult.status === "merged" || mergeResult.status === "already-merged") {
1709
3965
  console.log(`OK: PR merge confirmed (${pr.repoLabel}): ${pr.url}`);
@@ -1723,9 +3979,9 @@ async function runCompletionVerificationGate(projectRoot) {
1723
3979
  console.log(`
1724
3980
  [post] Auto-merge: skipped (not in policy completion.checks)`);
1725
3981
  }
1726
- const artifactDir = resolve2(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
1727
- mkdirSync2(artifactDir, { recursive: true });
1728
- writeFileSync2(resolve2(artifactDir, "review-status.txt"), failed ? `REJECTED
3982
+ const artifactDir = resolve6(paths.artifactsDir, safePathSegment2(taskId, { fallback: "task", maxLength: 96 }));
3983
+ mkdirSync3(artifactDir, { recursive: true });
3984
+ writeFileSync3(resolve6(artifactDir, "review-status.txt"), failed ? `REJECTED
1729
3985
  ` : `APPROVED
1730
3986
  `, "utf-8");
1731
3987
  if (!failed) {
@@ -1775,8 +4031,8 @@ async function runBunTool(args, cwd) {
1775
4031
  };
1776
4032
  }
1777
4033
  async function runProtoQualityGate(monorepoRoot) {
1778
- const protosDir = resolve2(monorepoRoot, "packages", "protos");
1779
- if (!existsSync2(protosDir)) {
4034
+ const protosDir = resolve6(monorepoRoot, "packages", "protos");
4035
+ if (!existsSync6(protosDir)) {
1780
4036
  console.log(`FAIL: Proto workspace not found at ${protosDir}`);
1781
4037
  return false;
1782
4038
  }
@@ -1803,7 +4059,7 @@ async function runProtoQualityGate(monorepoRoot) {
1803
4059
  console.log(generate.stderr || generate.stdout);
1804
4060
  ok = false;
1805
4061
  } else {
1806
- const drift = runCapture2(["git", "-C", protosDir, "status", "--porcelain", "--", "gen/ts"], monorepoRoot);
4062
+ const drift = runCapture3(["git", "-C", protosDir, "status", "--porcelain", "--", "gen/ts"], monorepoRoot);
1807
4063
  if (drift.exitCode !== 0) {
1808
4064
  console.log("FAIL: Could not inspect generated proto drift");
1809
4065
  console.log(drift.stderr || drift.stdout);
@@ -1824,12 +4080,12 @@ async function runProtoQualityGate(monorepoRoot) {
1824
4080
  } else {
1825
4081
  console.log("OK: Generated TypeScript compiles");
1826
4082
  }
1827
- const workflowPath = resolve2(monorepoRoot, ".github", "workflows", "pull-request-gate.yml");
1828
- if (!existsSync2(workflowPath)) {
4083
+ const workflowPath = resolve6(monorepoRoot, ".github", "workflows", "pull-request-gate.yml");
4084
+ if (!existsSync6(workflowPath)) {
1829
4085
  console.log(`FAIL: Missing workflow gate file at ${workflowPath}`);
1830
4086
  ok = false;
1831
4087
  } else {
1832
- const workflow = readFileSync(workflowPath, "utf-8");
4088
+ const workflow = readFileSync4(workflowPath, "utf-8");
1833
4089
  if (workflow.includes("if: false && needs.detect.outputs.protos_changed == 'true'")) {
1834
4090
  console.log("FAIL: Proto quality CI gate is disabled in pull-request-gate.yml");
1835
4091
  ok = false;
@@ -1840,13 +4096,13 @@ async function runProtoQualityGate(monorepoRoot) {
1840
4096
  return ok;
1841
4097
  }
1842
4098
  function repoHasRemoteRelevantCommits(projectRoot, repoRoot) {
1843
- const unpushed = runCapture2(["git", "-C", repoRoot, "log", "@{u}..HEAD", "--oneline"], projectRoot);
4099
+ const unpushed = runCapture3(["git", "-C", repoRoot, "log", "@{u}..HEAD", "--oneline"], projectRoot);
1844
4100
  if (unpushed.exitCode === 0 && unpushed.stdout.trim().length > 0)
1845
4101
  return true;
1846
4102
  if (unpushed.exitCode !== 0) {
1847
- const branch = runCapture2(["git", "-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], projectRoot);
4103
+ const branch = runCapture3(["git", "-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], projectRoot);
1848
4104
  if (branch.exitCode === 0 && branch.stdout.trim()) {
1849
- const remote = runCapture2(["git", "-C", repoRoot, "ls-remote", "--exit-code", "origin", `refs/heads/${branch.stdout.trim()}`], projectRoot);
4105
+ const remote = runCapture3(["git", "-C", repoRoot, "ls-remote", "--exit-code", "origin", `refs/heads/${branch.stdout.trim()}`], projectRoot);
1850
4106
  if (remote.exitCode !== 0)
1851
4107
  return true;
1852
4108
  }
@@ -1855,10 +4111,10 @@ function repoHasRemoteRelevantCommits(projectRoot, repoRoot) {
1855
4111
  }
1856
4112
  function repoHasPublishedTaskBranch(projectRoot, repoRoot, taskId) {
1857
4113
  const branchRef = resolveTaskBranchRef(projectRoot, taskId);
1858
- return runCapture2(["git", "-C", repoRoot, "ls-remote", "--exit-code", "origin", `refs/heads/${branchRef}`], projectRoot).exitCode === 0;
4114
+ return runCapture3(["git", "-C", repoRoot, "ls-remote", "--exit-code", "origin", `refs/heads/${branchRef}`], projectRoot).exitCode === 0;
1859
4115
  }
1860
4116
  async function readJsonFileIfPresent(path) {
1861
- if (!existsSync2(path)) {
4117
+ if (!existsSync6(path)) {
1862
4118
  return null;
1863
4119
  }
1864
4120
  try {
@@ -1869,9 +4125,9 @@ async function readJsonFileIfPresent(path) {
1869
4125
  }
1870
4126
  async function recordVerifierFailure(projectRoot, taskId, paths) {
1871
4127
  const failedApproachesPath = paths.failedApproachesPath;
1872
- const artifactDir = resolve2(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
1873
- const reviewStatePath = resolve2(artifactDir, "review-state.json");
1874
- const reviewFeedbackPath = resolve2(artifactDir, "review-feedback.md");
4128
+ const artifactDir = resolve6(paths.artifactsDir, safePathSegment2(taskId, { fallback: "task", maxLength: 96 }));
4129
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
4130
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1875
4131
  let summary = "Verifier rejected completion. Read review-feedback.md for required fixes.";
1876
4132
  const parsedReviewState = await readJsonFileIfPresent(reviewStatePath);
1877
4133
  if (parsedReviewState) {
@@ -1881,12 +4137,12 @@ async function recordVerifierFailure(projectRoot, taskId, paths) {
1881
4137
  }
1882
4138
  }
1883
4139
  let attempts = 1;
1884
- if (existsSync2(failedApproachesPath)) {
1885
- const content = readFileSync(failedApproachesPath, "utf-8");
1886
- attempts = (content.match(new RegExp(`^## ${escapeRegExp(taskId)}\\b`, "gm")) || []).length + 1;
4140
+ if (existsSync6(failedApproachesPath)) {
4141
+ const content = readFileSync4(failedApproachesPath, "utf-8");
4142
+ attempts = (content.match(new RegExp(`^## ${escapeRegExp2(taskId)}\\b`, "gm")) || []).length + 1;
1887
4143
  } else {
1888
- mkdirSync2(resolve2(failedApproachesPath, ".."), { recursive: true });
1889
- writeFileSync2(failedApproachesPath, `# Failed Approaches
4144
+ mkdirSync3(resolve6(failedApproachesPath, ".."), { recursive: true });
4145
+ writeFileSync3(failedApproachesPath, `# Failed Approaches
1890
4146
 
1891
4147
  `, "utf-8");
1892
4148
  }
@@ -1910,7 +4166,7 @@ async function recordTaskRepoCommits(projectRoot, taskId, paths) {
1910
4166
  statePaths.add(resolveHarnessPaths2(hostProjectRoot).taskRepoCommitsPath);
1911
4167
  }
1912
4168
  const repos = {};
1913
- const monoHead = runCapture2(["git", "-C", paths.monorepoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
4169
+ const monoHead = runCapture3(["git", "-C", paths.monorepoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
1914
4170
  if (monoHead) {
1915
4171
  repos["monorepo"] = monoHead;
1916
4172
  }
@@ -1924,13 +4180,17 @@ async function recordTaskRepoCommits(projectRoot, taskId, paths) {
1924
4180
  recorded_at: new Date().toISOString(),
1925
4181
  repos
1926
4182
  };
1927
- mkdirSync2(resolve2(statePath, ".."), { recursive: true });
1928
- writeFileSync2(statePath, `${JSON.stringify(state, null, 2)}
4183
+ mkdirSync3(resolve6(statePath, ".."), { recursive: true });
4184
+ writeFileSync3(statePath, `${JSON.stringify(state, null, 2)}
1929
4185
  `, "utf-8");
1930
4186
  }
1931
4187
  }
1932
4188
  var init_completion_verification = __esm(() => {
4189
+ init_policy();
4190
+ init_git_ops();
4191
+ init_pr_merge_gate_cap();
1933
4192
  init_verifier();
4193
+ init_task_data();
1934
4194
  });
1935
4195
 
1936
4196
  // packages/bundle-default-lifecycle/src/control-plane/task-verify.ts
@@ -1938,9 +4198,8 @@ var exports_task_verify = {};
1938
4198
  __export(exports_task_verify, {
1939
4199
  taskVerify: () => taskVerify
1940
4200
  });
1941
- import { currentTaskId as currentTaskId2 } from "@rig/runtime/control-plane/native/task-state";
1942
4201
  async function taskVerify(projectRoot, taskId) {
1943
- const activeTask = taskId || currentTaskId2(projectRoot);
4202
+ const activeTask = taskId || taskData().currentTaskId(projectRoot);
1944
4203
  if (!activeTask) {
1945
4204
  throw new Error("No active task.");
1946
4205
  }
@@ -1965,19 +4224,24 @@ async function taskVerify(projectRoot, taskId) {
1965
4224
  return true;
1966
4225
  }
1967
4226
  var init_task_verify = __esm(() => {
4227
+ init_task_data();
1968
4228
  init_verifier();
1969
4229
  });
1970
4230
 
1971
4231
  // packages/bundle-default-lifecycle/src/plugin.ts
1972
4232
  import { definePlugin } from "@rig/core/config";
4233
+ import { defineCapability as defineCapability4 } from "@rig/core/capability";
1973
4234
  import {
1974
- COMPLETION_VERIFICATION_CAPABILITY_ID,
1975
- TASK_VERIFY_CAPABILITY_ID
4235
+ COMPLETION_VERIFICATION_CAPABILITY,
4236
+ LIFECYCLE_GIT_AGENT,
4237
+ LIFECYCLE_TOOLCHAIN_SOURCES,
4238
+ RUN_CLOSEOUT_CAPABILITY,
4239
+ TASK_VERIFY_CAPABILITY
1976
4240
  } from "@rig/contracts";
1977
4241
 
1978
4242
  // packages/bundle-default-lifecycle/src/defaultPipeline.ts
1979
- import { resolveKernelStages } from "@rig/kernel/resolver";
1980
- import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
4243
+ import { createDefaultKernelPlugin } from "@rig/kernel-seed/default-kernel";
4244
+ import { resolveKernelStages } from "@rig/kernel-seed/resolver";
1981
4245
 
1982
4246
  // packages/bundle-default-lifecycle/src/stages/types.ts
1983
4247
  function defineDefaultLifecycleStage(input) {
@@ -1995,6 +4259,10 @@ var autoMergeStage = defineDefaultLifecycleStage({
1995
4259
  description: "Merge an approved PR using the repository default merge method through the runtime helper.",
1996
4260
  calls: ["runRepoDefaultMerge"]
1997
4261
  });
4262
+ async function runAutoMergeStage(input) {
4263
+ const { runRepoDefaultMerge: runRepoDefaultMerge2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
4264
+ await runRepoDefaultMerge2(input);
4265
+ }
1998
4266
 
1999
4267
  // packages/bundle-default-lifecycle/src/stages/commit.ts
2000
4268
  var commitStage = defineDefaultLifecycleStage({
@@ -2003,8 +4271,15 @@ var commitStage = defineDefaultLifecycleStage({
2003
4271
  description: "Commit the agent worktree changes using the runtime git closeout helper.",
2004
4272
  calls: ["commitRunChanges"]
2005
4273
  });
4274
+ async function runCommitStage(input) {
4275
+ const { commitRunChanges: commitRunChanges2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
4276
+ return await commitRunChanges2(input);
4277
+ }
2006
4278
 
2007
4279
  // packages/bundle-default-lifecycle/src/stages/isolation.ts
4280
+ import { ISOLATION_BACKEND } from "@rig/contracts";
4281
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
4282
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
2008
4283
  var isolationStage = defineDefaultLifecycleStage({
2009
4284
  id: "isolation",
2010
4285
  kind: "transform",
@@ -2027,6 +4302,23 @@ var mergeGateStage = defineDefaultLifecycleStage({
2027
4302
  description: "Enforce GitHub review state, required checks, and configured review gates through runtime PR automation.",
2028
4303
  calls: ["runStrictPrMergeGate"]
2029
4304
  });
4305
+ async function runMergeGateStage(input) {
4306
+ const { resolvePrMergeGateService: resolvePrMergeGateService2 } = await Promise.resolve().then(() => (init_pr_merge_gate_cap(), exports_pr_merge_gate_cap));
4307
+ const mergeGate = await resolvePrMergeGateService2(input.projectRoot);
4308
+ return await mergeGate.runGate({
4309
+ projectRoot: input.projectRoot,
4310
+ prUrl: input.prUrl,
4311
+ taskId: input.taskId,
4312
+ runId: input.runId,
4313
+ cycle: input.cycle,
4314
+ command: input.command,
4315
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
4316
+ ...input.allowedFailures !== undefined ? { allowedFailures: input.allowedFailures } : {},
4317
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
4318
+ ...input.final !== undefined ? { final: input.final } : {},
4319
+ ...input.requireGreptile !== undefined ? { requireGreptile: input.requireGreptile } : {}
4320
+ });
4321
+ }
2030
4322
 
2031
4323
  // packages/bundle-default-lifecycle/src/stages/open-pr.ts
2032
4324
  var openPrStage = defineDefaultLifecycleStage({
@@ -2035,6 +4327,24 @@ var openPrStage = defineDefaultLifecycleStage({
2035
4327
  description: "Open or reuse the closeout PR through the existing runtime PR automation seam.",
2036
4328
  calls: ["runPrAutomation"]
2037
4329
  });
4330
+ async function runOpenPrStage(input) {
4331
+ const { runPrAutomation: runPrAutomation2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
4332
+ return await runPrAutomation2({
4333
+ projectRoot: input.workspace,
4334
+ taskId: input.taskId,
4335
+ runId: input.runId,
4336
+ branch: input.branch,
4337
+ sourceTask: input.sourceTask ? { title: typeof input.sourceTask.title === "string" ? input.sourceTask.title : null } : null,
4338
+ command: input.command,
4339
+ gitCommand: input.gitCommand,
4340
+ steerPi: input.steerPi,
4341
+ ...input.config ? { config: input.config } : {},
4342
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
4343
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
4344
+ ...input.lifecycle !== undefined ? { lifecycle: input.lifecycle } : {},
4345
+ ...input.uploadedSnapshot !== undefined ? { uploadedSnapshot: input.uploadedSnapshot } : {}
4346
+ });
4347
+ }
2038
4348
 
2039
4349
  // packages/bundle-default-lifecycle/src/stages/push.ts
2040
4350
  var pushStage = defineDefaultLifecycleStage({
@@ -2043,6 +4353,10 @@ var pushStage = defineDefaultLifecycleStage({
2043
4353
  description: "Synchronize and push the task branch using the runtime closeout git helper.",
2044
4354
  calls: ["pushBranchSyncedWithOrigin"]
2045
4355
  });
4356
+ async function runPushStage(input) {
4357
+ const { pushBranchSyncedWithOrigin: pushBranchSyncedWithOrigin2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
4358
+ await pushBranchSyncedWithOrigin2(input);
4359
+ }
2046
4360
 
2047
4361
  // packages/bundle-default-lifecycle/src/stages/source-closeout.ts
2048
4362
  var sourceCloseoutStage = defineDefaultLifecycleStage({
@@ -2051,6 +4365,19 @@ var sourceCloseoutStage = defineDefaultLifecycleStage({
2051
4365
  description: "Reflect the merged PR into the task source using the existing runtime closeout helper.",
2052
4366
  calls: ["closeIssueAfterMergedPr"]
2053
4367
  });
4368
+ async function runSourceCloseoutStage(input) {
4369
+ if (input.pr.status !== "merged" || !input.pr.prUrl)
4370
+ return;
4371
+ const { closeIssueAfterMergedPr: closeIssueAfterMergedPr2 } = await Promise.resolve().then(() => (init_pr_automation(), exports_pr_automation));
4372
+ await closeIssueAfterMergedPr2({
4373
+ projectRoot: input.projectRoot,
4374
+ taskId: input.taskId,
4375
+ runId: input.runId,
4376
+ prUrl: input.pr.prUrl,
4377
+ updateTaskSource: input.updateTaskSource,
4378
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {}
4379
+ });
4380
+ }
2054
4381
 
2055
4382
  // packages/bundle-default-lifecycle/src/stages/validate.ts
2056
4383
  var validateStage = defineDefaultLifecycleStage({
@@ -2059,6 +4386,9 @@ var validateStage = defineDefaultLifecycleStage({
2059
4386
  description: "Run plugin-host validators against the isolated worktree before closeout side effects.",
2060
4387
  calls: ["taskValidate"]
2061
4388
  });
4389
+ async function runValidateStage(input, runner) {
4390
+ return await runner(input);
4391
+ }
2062
4392
 
2063
4393
  // packages/bundle-default-lifecycle/src/stages/verify.ts
2064
4394
  var verifyStage = defineDefaultLifecycleStage({
@@ -2237,7 +4567,306 @@ var defaultLifecycleCliCommands = [
2237
4567
  }
2238
4568
  ];
2239
4569
 
4570
+ // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
4571
+ import { resolve } from "path";
4572
+ import { loadConfig } from "@rig/core/load-config";
4573
+ import { resolvePluginHost as resolvePluginHost2 } from "@rig/core/project-plugins";
4574
+ import { createDefaultKernel } from "@rig/kernel-seed/default-kernel";
4575
+ import { buildPluginHostContext } from "@rig/core/plugin-host-context";
4576
+
4577
+ // packages/bundle-default-lifecycle/src/native/in-process-closeout.ts
4578
+ class CloseoutValidationError extends Error {
4579
+ name = "CloseoutValidationError";
4580
+ }
4581
+
4582
+ // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
4583
+ init_task_data();
4584
+ function cleanString(value) {
4585
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
4586
+ }
4587
+ function closeoutOutcome(status) {
4588
+ switch (status) {
4589
+ case "completed":
4590
+ return "completed";
4591
+ case "failed":
4592
+ case "needs-attention":
4593
+ return "failed";
4594
+ case "pending":
4595
+ case "running":
4596
+ return "started";
4597
+ }
4598
+ }
4599
+ async function loadRigAutomationConfig(projectRoot) {
4600
+ return await loadConfig(projectRoot);
4601
+ }
4602
+ async function runRigProjectValidation({ projectRoot, taskId }) {
4603
+ const pluginHostCtx = await buildPluginHostContext(projectRoot);
4604
+ return taskData().taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
4605
+ }
4606
+ function shouldAttemptRigMerge2(config) {
4607
+ const mode = config.merge?.mode;
4608
+ return mode !== "off" && mode !== "pr-ready";
4609
+ }
4610
+ async function loadPluginStageContributions(projectRoot) {
4611
+ const { host } = await resolvePluginHost2(projectRoot);
4612
+ return { executors: host.listStageExecutors(), mutations: host.listStageMutations() };
4613
+ }
4614
+ async function runPipelineCloseout(input) {
4615
+ const taskId = cleanString(input.taskId);
4616
+ if (!taskId) {
4617
+ throw new Error("Pipeline closeout requires a task id.");
4618
+ }
4619
+ const loadedConfig = input.config ?? await loadRigAutomationConfig(input.projectRoot);
4620
+ const prMode = loadedConfig?.pr?.mode ?? "off";
4621
+ const reviewProvider = loadedConfig?.review?.provider ?? "github";
4622
+ const effectiveConfig = {
4623
+ ...loadedConfig ?? {},
4624
+ pr: { ...loadedConfig?.pr ?? {}, mode: prMode },
4625
+ review: { ...loadedConfig?.review ?? {}, provider: reviewProvider }
4626
+ };
4627
+ const openOnlyConfig = {
4628
+ ...effectiveConfig,
4629
+ merge: { ...effectiveConfig.merge ?? {}, mode: "pr-ready" }
4630
+ };
4631
+ const shouldMerge = shouldAttemptRigMerge2(effectiveConfig);
4632
+ const workspace = input.workspace;
4633
+ const artifactRoot = input.artifactRoot ?? resolve(input.projectRoot, "artifacts", taskId);
4634
+ const journal = async (phase, status, detail) => {
4635
+ await input.journalPhase(phase, closeoutOutcome(status), detail ?? null);
4636
+ };
4637
+ if (prMode === "off" || prMode === "ask") {
4638
+ const reason = prMode === "ask" ? "PR creation awaits operator approval." : "PR automation disabled.";
4639
+ await input.reflect("under_review", reason);
4640
+ await journal("completed", "completed", reason);
4641
+ return {
4642
+ mode: "pipeline",
4643
+ pipelineStageIds: [...resolveDefaultLifecycle().order],
4644
+ result: { status: "skipped", iterations: 0, feedback: [] }
4645
+ };
4646
+ }
4647
+ const state = {
4648
+ branch: input.branch,
4649
+ validationPassed: false,
4650
+ committed: false,
4651
+ pushed: false,
4652
+ prUrl: null,
4653
+ prReady: false,
4654
+ pr: null,
4655
+ gate: null,
4656
+ mergeGate: null,
4657
+ merged: false,
4658
+ iterations: 0,
4659
+ feedback: [],
4660
+ blockedDetail: null
4661
+ };
4662
+ const cont = (ctx2) => ({ kind: "continue", ctx: ctx2 });
4663
+ const executors = {
4664
+ isolation: (ctx2) => cont(ctx2),
4665
+ validate: async (ctx2) => {
4666
+ await input.onValidationStart?.();
4667
+ await journal("queued", "running", `Validating task ${taskId} before closeout.`);
4668
+ let passed = false;
4669
+ try {
4670
+ passed = await runValidateStage({ projectRoot: input.projectRoot, taskId }, input.runValidation ?? runRigProjectValidation);
4671
+ } catch (error) {
4672
+ const detail = `Rig validation failed before closeout: ${error instanceof Error ? error.message : String(error)}`;
4673
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
4674
+ throw new CloseoutValidationError(detail);
4675
+ }
4676
+ if (!passed) {
4677
+ const detail = `Rig validation failed for task ${taskId}; closeout blocked before commit.`;
4678
+ await input.reflect("needs_attention", "Rig validation failed before closeout; commit/push/PR automation is blocked.", { errorText: detail });
4679
+ throw new CloseoutValidationError(detail);
4680
+ }
4681
+ state.validationPassed = true;
4682
+ await journal("queued", "completed", `Validation passed for task ${taskId}.`);
4683
+ const workspaceBranch = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
4684
+ const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? cleanString(workspaceBranch.stdout) : null;
4685
+ if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== state.branch) {
4686
+ state.branch = currentWorkspaceBranch;
4687
+ }
4688
+ return cont(ctx2);
4689
+ },
4690
+ verify: () => state.validationPassed ? { kind: "allow" } : { kind: "block", reason: "validation did not pass" },
4691
+ commit: async (ctx2) => {
4692
+ await journal("commit", "running", `Committing changes in ${workspace}.`);
4693
+ const committed = await runCommitStage({ cwd: workspace, message: `rig: complete task ${taskId}`, command: input.gitCommand });
4694
+ state.committed = committed.committed;
4695
+ return cont(ctx2);
4696
+ },
4697
+ push: async (ctx2) => {
4698
+ await journal("push", "running", `Pushing branch ${state.branch}.`);
4699
+ await runPushStage({ projectRoot: workspace, branch: state.branch, gitCommand: input.gitCommand });
4700
+ state.pushed = true;
4701
+ return cont(ctx2);
4702
+ },
4703
+ "open-pr": async (ctx2) => {
4704
+ await journal("pr-review-merge", "running", `Opening a pull request for ${state.branch}.`);
4705
+ const pr = await runOpenPrStage({
4706
+ ...input,
4707
+ taskId,
4708
+ branch: state.branch,
4709
+ artifactRoot,
4710
+ config: openOnlyConfig,
4711
+ sourceTask: { title: cleanString(input.sourceTask?.title) },
4712
+ lifecycle: {
4713
+ onPrOpened: async ({ prUrl }) => {
4714
+ await journal("pr-opened", "running", prUrl);
4715
+ await input.reflect("under_review", "Rig opened a pull request for this task.");
4716
+ },
4717
+ onFeedback: async ({ feedback }) => {
4718
+ await input.reflect("ci_fixing", "Rig is fixing CI/review feedback for this task.", { errorText: feedback.join(`
4719
+ `) || null });
4720
+ }
4721
+ }
4722
+ });
4723
+ state.pr = pr;
4724
+ state.prUrl = pr.prUrl ?? null;
4725
+ state.prReady = pr.status === "opened" || pr.status === "merged";
4726
+ state.iterations = pr.iterations;
4727
+ state.feedback = [...pr.actionableFeedback];
4728
+ if (pr.status === "needs_attention") {
4729
+ state.blockedDetail = pr.actionableFeedback.join(`
4730
+ `) || "PR automation did not produce a mergeable PR.";
4731
+ }
4732
+ return cont(ctx2);
4733
+ },
4734
+ "merge-gate": async (ctx2) => {
4735
+ if (!shouldMerge || !state.prReady || !state.prUrl) {
4736
+ state.mergeGate = state.prReady ? "skipped" : null;
4737
+ return state.prReady ? cont(ctx2) : { kind: "block", reason: state.blockedDetail ?? "no mergeable PR to gate" };
4738
+ }
4739
+ const gate = await runMergeGateStage({
4740
+ projectRoot: workspace,
4741
+ prUrl: state.prUrl,
4742
+ taskId,
4743
+ runId: input.runId,
4744
+ cycle: 1,
4745
+ command: input.command,
4746
+ artifactRoot,
4747
+ final: true,
4748
+ ...effectiveConfig.merge?.allowedFailures ? { allowedFailures: effectiveConfig.merge.allowedFailures } : {},
4749
+ ...input.greptileApi !== undefined ? { greptileApi: input.greptileApi } : {},
4750
+ requireGreptile: reviewProvider === "greptile"
4751
+ });
4752
+ state.gate = gate;
4753
+ if (gate.approved) {
4754
+ state.mergeGate = "passed";
4755
+ return cont(ctx2);
4756
+ }
4757
+ state.mergeGate = "blocked";
4758
+ const detail = gate.actionableFeedback.join(`
4759
+ `) || gate.reasons.join("; ") || "merge gate blocked the PR.";
4760
+ state.blockedDetail = detail;
4761
+ await input.reflect("needs_attention", "Rig needs operator attention before this task can merge.", { errorText: detail });
4762
+ await journal("pr-review-merge", "needs-attention", detail);
4763
+ return { kind: "block", reason: detail };
4764
+ },
4765
+ "auto-merge": async (ctx2) => {
4766
+ if (!shouldMerge || state.mergeGate !== "passed" || !state.prUrl || !state.gate) {
4767
+ return cont(ctx2);
4768
+ }
4769
+ await journal("merge", "running", state.prUrl);
4770
+ await input.reflect("merging", "Rig is merging the pull request for this task.");
4771
+ await runAutoMergeStage({ prUrl: state.prUrl, config: effectiveConfig, command: input.command, cwd: workspace, strictGate: state.gate });
4772
+ state.merged = true;
4773
+ return cont(ctx2);
4774
+ },
4775
+ "source-closeout": async (ctx2) => {
4776
+ if (!state.merged || !state.prUrl)
4777
+ return cont(ctx2);
4778
+ await journal("close-source", "running", state.prUrl);
4779
+ const mergedPr = { ...state.pr ?? { iterations: state.iterations, actionableFeedback: state.feedback }, status: "merged", prUrl: state.prUrl, merged: true };
4780
+ await runSourceCloseoutStage({
4781
+ projectRoot: input.projectRoot,
4782
+ taskId,
4783
+ runId: input.runId,
4784
+ pr: mergedPr,
4785
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
4786
+ updateTaskSource: async () => {
4787
+ await input.reflect("closed", "Rig merged the pull request and closed this task source.");
4788
+ return { updated: true, taskId, status: "closed", source: "runtime", sourceKind: "runtime" };
4789
+ }
4790
+ });
4791
+ return cont(ctx2);
4792
+ },
4793
+ "journal-append": (ctx2) => cont(ctx2)
4794
+ };
4795
+ const defaultLifecyclePlugin = createDefaultLifecyclePlugin(executors);
4796
+ const pluginStages = await loadPluginStageContributions(input.projectRoot);
4797
+ const kernel = createDefaultKernel({ ...input.kernelJournal ? { journal: input.kernelJournal } : {}, stageExecutors: { ...executors, ...pluginStages.executors } });
4798
+ const resolved = kernel.stageRunner.resolve(defaultLifecyclePlugin.contributes?.stages ?? [], pluginStages.mutations);
4799
+ const ctx = {
4800
+ runId: input.runId,
4801
+ taskId,
4802
+ state,
4803
+ metadata: { projectRoot: input.projectRoot, workspace, closeoutState: state }
4804
+ };
4805
+ await kernel.stageRunner.runPipeline(input.runId, resolved, ctx);
4806
+ const result = mapStateToResult(state);
4807
+ if (result.status === "merged" || result.status === "opened") {
4808
+ await journal("completed", "completed", result.prUrl ? `${result.status === "merged" ? "PR merged and issue closed" : "PR ready without merge"}: ${result.prUrl}` : result.status);
4809
+ }
4810
+ return { mode: "pipeline", pipelineStageIds: [...resolved.order], result };
4811
+ }
4812
+ function mapStateToResult(state) {
4813
+ if (state.merged && state.prUrl) {
4814
+ return { status: "merged", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
4815
+ }
4816
+ if (state.mergeGate === "blocked" || state.blockedDetail) {
4817
+ return {
4818
+ status: "needs-attention",
4819
+ ...state.prUrl ? { prUrl: state.prUrl } : {},
4820
+ iterations: state.iterations,
4821
+ feedback: state.feedback
4822
+ };
4823
+ }
4824
+ if (state.prReady && state.prUrl) {
4825
+ return { status: "opened", prUrl: state.prUrl, iterations: state.iterations, feedback: state.feedback };
4826
+ }
4827
+ return { status: "needs-attention", ...state.prUrl ? { prUrl: state.prUrl } : {}, iterations: state.iterations, feedback: state.feedback };
4828
+ }
4829
+
4830
+ // packages/bundle-default-lifecycle/src/native/closeout-runners.ts
4831
+ init_github_auth_env();
4832
+ function commandEnv(env) {
4833
+ const token = resolveGitHubAuthToken(env);
4834
+ return token ? { ...env, RIG_GITHUB_TOKEN: token, GH_TOKEN: token, GITHUB_TOKEN: token } : { ...env };
4835
+ }
4836
+ function createBunCommandRunner(binary, env) {
4837
+ return async (args, options) => {
4838
+ try {
4839
+ const child = Bun.spawn([binary, ...args], {
4840
+ ...options?.cwd !== undefined ? { cwd: options.cwd } : {},
4841
+ env: commandEnv(env),
4842
+ stdin: "ignore",
4843
+ stdout: "pipe",
4844
+ stderr: "pipe"
4845
+ });
4846
+ const [exitCode, stdout, stderr] = await Promise.all([
4847
+ child.exited,
4848
+ new Response(child.stdout).text(),
4849
+ new Response(child.stderr).text()
4850
+ ]);
4851
+ return { exitCode, stdout, stderr };
4852
+ } catch (error) {
4853
+ return {
4854
+ exitCode: 1,
4855
+ stdout: "",
4856
+ stderr: error instanceof Error ? error.message : String(error)
4857
+ };
4858
+ }
4859
+ };
4860
+ }
4861
+ function createEnvCloseoutRunners(env = process.env) {
4862
+ return {
4863
+ command: createBunCommandRunner("gh", env),
4864
+ gitCommand: createBunCommandRunner("git", env)
4865
+ };
4866
+ }
4867
+
2240
4868
  // packages/bundle-default-lifecycle/src/plugin.ts
4869
+ init_git_ops();
2241
4870
  var DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
2242
4871
  var COMPLETION_VERIFICATION_HOOK_ID = "@rig/bundle-default-lifecycle:completion-verification";
2243
4872
  async function runCompletionGate(projectRoot) {
@@ -2261,26 +4890,85 @@ var LIFECYCLE_HOOKS = [
2261
4890
  handler: completionVerificationStopHandler
2262
4891
  }
2263
4892
  ];
4893
+ var TaskVerifyCap = defineCapability4(TASK_VERIFY_CAPABILITY);
4894
+ var CompletionVerificationCap = defineCapability4(COMPLETION_VERIFICATION_CAPABILITY);
4895
+ var RunCloseoutCap = defineCapability4(RUN_CLOSEOUT_CAPABILITY);
4896
+ var LifecycleGitAgentCap = defineCapability4(LIFECYCLE_GIT_AGENT);
4897
+ var ToolchainSourcesCap = defineCapability4(LIFECYCLE_TOOLCHAIN_SOURCES);
4898
+ var LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION = {
4899
+ hookBinaries: [
4900
+ {
4901
+ name: "inject-context",
4902
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/inject-context.ts"
4903
+ },
4904
+ {
4905
+ name: "task-runtime-start",
4906
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/task-runtime-start.ts"
4907
+ }
4908
+ ]
4909
+ };
2264
4910
  var LIFECYCLE_CAPABILITIES = [
2265
4911
  { id: "default-lifecycle.pipeline", title: "Default lifecycle pipeline", commandId: DEFAULT_PIPELINE_CLI_ID },
2266
4912
  { id: "default-lifecycle.kernel-status", title: "Default kernel status", commandId: DEFAULT_KERNEL_CLI_ID },
2267
- {
2268
- id: TASK_VERIFY_CAPABILITY_ID,
4913
+ TaskVerifyCap.provide(() => async (input) => {
4914
+ const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
4915
+ return taskVerify2(input.projectRoot, input.taskId);
4916
+ }, {
2269
4917
  title: "Task verification",
2270
- description: "Verify a task's changes (local checks + AI review) and report approval.",
2271
- run: async (input) => {
2272
- const { projectRoot, taskId } = input;
2273
- const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
2274
- return taskVerify2(projectRoot, taskId);
4918
+ description: "Verify a task's changes (local checks + AI review) and report approval."
4919
+ }),
4920
+ CompletionVerificationCap.provide(() => async (input) => runCompletionGate(input.projectRoot), {
4921
+ title: "Completion verification gate",
4922
+ description: "Run the full completion gate for the active task and report whether it passed."
4923
+ }),
4924
+ RunCloseoutCap.provide(() => async (input) => {
4925
+ const closeout = await runPipelineCloseout({ ...input, ...createEnvCloseoutRunners(process.env) });
4926
+ return closeout.result;
4927
+ }, {
4928
+ title: "Run closeout",
4929
+ description: "Run the default lifecycle closeout driver for a completed OMP run."
4930
+ }),
4931
+ LifecycleGitAgentCap.provide(() => ({
4932
+ shouldScopeGitCommit,
4933
+ gitStatus,
4934
+ gitChanged,
4935
+ gitPreflight,
4936
+ gitSyncBranch,
4937
+ gitCommit,
4938
+ gitSnapshot,
4939
+ gitOpenPr
4940
+ }), {
4941
+ title: "Lifecycle git agent commands",
4942
+ description: "Task-aware git helpers used by the provider-owned rig-agent command surface."
4943
+ }),
4944
+ ToolchainSourcesCap.provide(() => LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION, {
4945
+ title: "Lifecycle toolchain sources",
4946
+ description: "Source paths for the lifecycle control-plane hook binaries (inject-context, task-runtime-start), compiled by the isolation runtime toolchain."
4947
+ })
4948
+ ];
4949
+ async function runLifecycleHookRunner(hookId, role) {
4950
+ process.env.RIG_HOOK_ROLE = role;
4951
+ const { main } = await import("@rig/core/hook-runner");
4952
+ await main(["--plugin", DEFAULT_LIFECYCLE_PLUGIN_ID, "--hook", hookId]);
4953
+ }
4954
+ var LIFECYCLE_SEED_ENTRYPOINTS = [
4955
+ {
4956
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:completion-verification-entrypoint`,
4957
+ basename: "completion-verification",
4958
+ run: ({ basename }) => runLifecycleHookRunner(COMPLETION_VERIFICATION_HOOK_ID, basename ?? "completion-verification")
4959
+ },
4960
+ {
4961
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:inject-context-entrypoint`,
4962
+ basename: "inject-context",
4963
+ run: async () => {
4964
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/inject-context");
2275
4965
  }
2276
4966
  },
2277
4967
  {
2278
- id: COMPLETION_VERIFICATION_CAPABILITY_ID,
2279
- title: "Completion verification gate",
2280
- description: "Run the full completion gate for the active task and report whether it passed.",
2281
- run: async (input) => {
2282
- const { projectRoot } = input;
2283
- return runCompletionGate(projectRoot);
4968
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:task-runtime-start-entrypoint`,
4969
+ basename: "task-runtime-start",
4970
+ run: async () => {
4971
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/task-runtime-start");
2284
4972
  }
2285
4973
  }
2286
4974
  ];
@@ -2290,10 +4978,20 @@ function createDefaultLifecyclePlugin(stages = {}) {
2290
4978
  version: "0.0.0-alpha.1",
2291
4979
  provides: [],
2292
4980
  contributes: {
2293
- stages: defaultLifecycleStages.map((stage) => stages[stage.id] ? { ...stage, run: stages[stage.id] } : stage),
4981
+ stages: defaultLifecycleStages.map((stage) => {
4982
+ const run = Object.prototype.hasOwnProperty.call(stages, stage.id) ? stages[stage.id] : undefined;
4983
+ return run ? { ...stage, run } : stage;
4984
+ }),
2294
4985
  hooks: LIFECYCLE_HOOKS,
2295
4986
  capabilities: LIFECYCLE_CAPABILITIES,
2296
- cliCommands: defaultLifecycleCliCommands
4987
+ cliCommands: defaultLifecycleCliCommands,
4988
+ seedEntrypoints: LIFECYCLE_SEED_ENTRYPOINTS,
4989
+ config: {
4990
+ defaults: () => ({
4991
+ merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },
4992
+ automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 }
4993
+ })
4994
+ }
2297
4995
  }
2298
4996
  });
2299
4997
  }