@h-rig/bundle-default-lifecycle 0.0.6-alpha.157 → 0.0.6-alpha.159

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
@@ -16,6 +16,35 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  var __require = import.meta.require;
18
18
 
19
+ // packages/bundle-default-lifecycle/src/control-plane/task-data.ts
20
+ import { TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
21
+ import { defineCapability } from "@rig/core/capability";
22
+ import { requireInstalledCapability } from "@rig/core/capability-loaders";
23
+ function taskData() {
24
+ return requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before running the lifecycle.");
25
+ }
26
+ var TaskDataCap;
27
+ var init_task_data = __esm(() => {
28
+ TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
29
+ });
30
+
31
+ // packages/bundle-default-lifecycle/src/control-plane/pr-merge-gate-cap.ts
32
+ var exports_pr_merge_gate_cap = {};
33
+ __export(exports_pr_merge_gate_cap, {
34
+ resolvePrMergeGateService: () => resolvePrMergeGateService
35
+ });
36
+ import { PR_MERGE_GATE } from "@rig/contracts";
37
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
38
+ import { resolvePluginHost } from "@rig/core/project-plugins";
39
+ async function resolvePrMergeGateService(projectRoot) {
40
+ const { host } = await resolvePluginHost(projectRoot);
41
+ return PrMergeGateCap.require(host);
42
+ }
43
+ var PrMergeGateCap;
44
+ var init_pr_merge_gate_cap = __esm(() => {
45
+ PrMergeGateCap = defineCapability2(PR_MERGE_GATE);
46
+ });
47
+
19
48
  // packages/bundle-default-lifecycle/src/control-plane/pr-automation.ts
20
49
  var exports_pr_automation = {};
21
50
  __export(exports_pr_automation, {
@@ -33,11 +62,7 @@ __export(exports_pr_automation, {
33
62
  buildPrAutomationBody: () => buildPrAutomationBody,
34
63
  UPLOADED_SNAPSHOT_PR_MARKER: () => UPLOADED_SNAPSHOT_PR_MARKER
35
64
  });
36
- import { assertSafeGitBranchName } from "@rig/shared/safe-identifiers";
37
- import { runStrictPrMergeGate } from "@rig/pr-review-plugin";
38
- import {
39
- strictMergeHeadShaFromGate
40
- } from "@rig/contracts";
65
+ import { assertSafeGitBranchName } from "@rig/core/safe-identifiers";
41
66
  function positiveInt(value, fallback) {
42
67
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
43
68
  }
@@ -340,7 +365,7 @@ async function commitRunChanges(input) {
340
365
  async function closeIssueAfterMergedPr(input) {
341
366
  await input.updateTaskSource(input.projectRoot, {
342
367
  taskId: input.taskId,
343
- sourceTask: input.sourceTask,
368
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
344
369
  update: {
345
370
  status: "closed",
346
371
  comment: [
@@ -386,11 +411,12 @@ async function runRepoDefaultMerge(input) {
386
411
  if (merge.mode === "off")
387
412
  return;
388
413
  const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
389
- const matchHeadSha = strictMergeHeadShaFromGate(input.strictGate, input.prUrl, requireGreptile);
414
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot ?? input.cwd ?? process.cwd());
415
+ const matchHeadSha = mergeGate.resolveHeadSha({ result: input.strictGate, prUrl: input.prUrl, requireGreptile });
390
416
  const method = merge.method ?? "repo-default";
391
417
  const args = ["pr", "merge", input.prUrl];
392
418
  if (method === "repo-default") {
393
- args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, cwd: input.cwd }));
419
+ args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, ...input.cwd !== undefined ? { cwd: input.cwd } : {} }));
394
420
  } else {
395
421
  args.push(`--${method}`);
396
422
  }
@@ -467,6 +493,7 @@ async function syncBranchAfterPrFeedback(input) {
467
493
  }
468
494
  async function runPrAutomation(input) {
469
495
  const branch = assertSafeGitBranchName(input.branch, "PR branch");
496
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot);
470
497
  const prConfig = input.config?.pr ?? {};
471
498
  const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
472
499
  if (prConfig.mode === "off" || prConfig.mode === "ask") {
@@ -476,7 +503,7 @@ async function runPrAutomation(input) {
476
503
  taskId: input.taskId,
477
504
  runId: input.runId,
478
505
  summary: input.sourceTask?.title ? `Rig completed: ${input.sourceTask.title}` : null,
479
- uploadedSnapshot: input.uploadedSnapshot
506
+ ...input.uploadedSnapshot !== undefined ? { uploadedSnapshot: input.uploadedSnapshot } : {}
480
507
  });
481
508
  if (input.gitCommand) {
482
509
  await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
@@ -548,16 +575,16 @@ ${createResult.stdout ?? ""}`) : null;
548
575
  await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
549
576
  continue;
550
577
  }
551
- const gate = await runStrictPrMergeGate({
578
+ const gate = await mergeGate.runGate({
552
579
  projectRoot: input.projectRoot,
553
580
  prUrl,
554
581
  taskId: input.taskId,
555
582
  runId: input.runId,
556
583
  cycle: iteration,
557
584
  command: input.command,
558
- artifactRoot: input.artifactRoot,
585
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
559
586
  allowedFailures: input.config?.merge?.allowedFailures ?? [],
560
- greptileApi: requireGreptile ? input.greptileApi : undefined,
587
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
561
588
  requireGreptile
562
589
  });
563
590
  latestFeedback = [...gate.actionableFeedback];
@@ -587,22 +614,22 @@ ${createResult.stdout ?? ""}`) : null;
587
614
  }
588
615
  if (gate.approved) {
589
616
  pendingElapsedMs = 0;
590
- const finalGate = await runStrictPrMergeGate({
617
+ const finalGate = await mergeGate.runGate({
591
618
  projectRoot: input.projectRoot,
592
619
  prUrl,
593
620
  taskId: input.taskId,
594
621
  runId: input.runId,
595
622
  cycle: iteration,
596
623
  command: input.command,
597
- artifactRoot: input.artifactRoot,
624
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
598
625
  allowedFailures: input.config?.merge?.allowedFailures ?? [],
599
- greptileApi: requireGreptile ? input.greptileApi : undefined,
626
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
600
627
  requireGreptile,
601
628
  final: true
602
629
  });
603
630
  if (finalGate.approved) {
604
631
  await input.lifecycle?.onMergeStarted?.({ prUrl });
605
- await runRepoDefaultMerge({ prUrl, config: input.config, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
632
+ await runRepoDefaultMerge({ prUrl, ...input.config !== undefined ? { config: input.config } : {}, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
606
633
  await input.lifecycle?.onMerged?.({ prUrl });
607
634
  return { status: "merged", prUrl, iterations: iteration, actionableFeedback: [], merged: true };
608
635
  }
@@ -649,6 +676,7 @@ ${createResult.stdout ?? ""}`) : null;
649
676
  }
650
677
  var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->", RIG_RUNTIME_COMMIT_EXCLUDES, GREPTILE_REREVIEW_MARKER_PREFIX = "rig:greptile-rereview";
651
678
  var init_pr_automation = __esm(() => {
679
+ init_pr_merge_gate_cap();
652
680
  RIG_RUNTIME_COMMIT_EXCLUDES = [
653
681
  ".rig",
654
682
  "artifacts",
@@ -656,31 +684,1619 @@ var init_pr_automation = __esm(() => {
656
684
  ];
657
685
  });
658
686
 
659
- // packages/bundle-default-lifecycle/src/control-plane/verifier.ts
660
- import { existsSync, mkdirSync, writeFileSync } from "fs";
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";
661
711
  import { resolve } from "path";
662
- import { resolveRuntimeSecrets } from "@rig/runtime/control-plane/runtime/baked-secrets";
663
- import { readPrMetadata } from "@rig/runtime/control-plane/native/git-ops";
664
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
665
- import { readConfiguredTaskSourceTask } from "@rig/runtime/control-plane/tasks/source-lifecycle";
666
- import { artifactDirForId, lookupTask, readTaskConfig } from "@rig/runtime/control-plane/native/task-state";
667
- import { nowIso, resolveHarnessPaths, runCapture } from "@rig/runtime/control-plane/native/utils";
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(resolve(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 resolve2 } 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(resolve2(runtimeWorkspace, ".git"))) {
780
+ return resolve2(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(resolve2(projectRoot, ".git"))) {
843
+ console.log(`ERROR: project root is not a git repo (${projectRoot})`);
844
+ issues += 1;
845
+ }
846
+ if (monorepoRoot && existsSync3(resolve2(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(resolve2(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) : resolve2(resolve2(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(resolve2(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(resolve2(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 = resolve2(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 = resolve2(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 resolve2(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 = resolve2(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(resolve2(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 = resolve2(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(resolve2(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(resolve2(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(resolve2(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 = resolve2(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 = resolve2(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 = resolve2(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(resolve2(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 = resolve2(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(resolve2(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 ? resolve2(runtimeRoot, "home") : "";
1860
+ const runtimeTmp = runtimeRoot ? resolve2(runtimeRoot, "tmp") : "";
1861
+ const runtimeCache = runtimeRoot ? resolve2(runtimeRoot, "cache") : "";
1862
+ const runtimeKnownHosts = runtimeHome ? resolve2(runtimeHome, ".ssh", "known_hosts") : "";
1863
+ const runtimeKey = runtimeHome ? resolve2(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 = resolve2(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 = resolve2(runtimeHome, ".ssl");
1970
+ const sslConfig = resolve2(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(resolve2(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 = resolve2(startPath);
2010
+ while (true) {
2011
+ const candidate = resolve2(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 resolve3 } from "path";
668
2053
  import {
669
- collectPrReviewEvidence,
670
- evaluateStrictPrMergeGate,
671
- parseGreptileScore,
672
- stripHtml
673
- } 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 = resolve3(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 resolve4 } 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
+ }
674
2289
  async function verifyTask(options) {
2290
+ await ensureMergeGate(options.projectRoot);
675
2291
  const paths = resolveHarnessPaths(options.projectRoot);
676
2292
  const taskId = options.taskId;
677
- const normalizedTaskId = lookupTask(options.projectRoot, taskId);
678
- const artifactDir = artifactDirForId(options.projectRoot, taskId);
679
- mkdirSync(artifactDir, { recursive: true });
680
- const validationSummaryPath = resolve(artifactDir, "validation-summary.json");
681
- const reviewFeedbackPath = resolve(artifactDir, "review-feedback.md");
682
- const reviewStatePath = resolve(artifactDir, "review-state.json");
683
- 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 = resolve4(artifactDir, "validation-summary.json");
2297
+ const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
2298
+ const reviewStatePath = resolve4(artifactDir, "review-state.json");
2299
+ const greptileRawPath = resolve4(artifactDir, "review-greptile-raw.json");
684
2300
  const prStates = readPrMetadata(options.projectRoot, taskId);
685
2301
  const prState = prStates[0] || null;
686
2302
  const localReasons = [];
@@ -692,7 +2308,7 @@ async function verifyTask(options) {
692
2308
  if (!normalizedTaskId && !await hasConfiguredSourceTask(options.projectRoot, taskId)) {
693
2309
  localReasons.push(`[Task Config] Unknown task id '${taskId}' in task-config or configured task source.`);
694
2310
  }
695
- if (!existsSync(validationSummaryPath)) {
2311
+ if (!existsSync5(validationSummaryPath)) {
696
2312
  localReasons.push(`[Artifact Quality] validation-summary.json not found at ${validationSummaryPath}.`);
697
2313
  } else {
698
2314
  const summary = await parseValidationSummary(validationSummaryPath);
@@ -701,13 +2317,13 @@ async function verifyTask(options) {
701
2317
  }
702
2318
  }
703
2319
  for (const file of ["task-result.json", "decision-log.md", "next-actions.md", "changed-files.txt"]) {
704
- const requiredPath = resolve(artifactDir, file);
705
- if (!existsSync(requiredPath)) {
2320
+ const requiredPath = resolve4(artifactDir, file);
2321
+ if (!existsSync5(requiredPath)) {
706
2322
  localReasons.push(`[Artifact Quality] Missing required artifact file: ${requiredPath}`);
707
2323
  }
708
2324
  }
709
- const taskResultPath = resolve(artifactDir, "task-result.json");
710
- if (existsSync(taskResultPath)) {
2325
+ const taskResultPath = resolve4(artifactDir, "task-result.json");
2326
+ if (existsSync5(taskResultPath)) {
711
2327
  const taskResult = await readJsonFile(taskResultPath);
712
2328
  const artifactStatus = typeof taskResult?.status === "string" ? taskResult.status.trim().toLowerCase() : "";
713
2329
  if (artifactStatus === "partial") {
@@ -720,8 +2336,8 @@ async function verifyTask(options) {
720
2336
  localReasons.push("[Artifact Quality] task-result.json next actions indicate remaining implementation scope.");
721
2337
  }
722
2338
  }
723
- const nextActionsPath = resolve(artifactDir, "next-actions.md");
724
- if (existsSync(nextActionsPath)) {
2339
+ const nextActionsPath = resolve4(artifactDir, "next-actions.md");
2340
+ if (existsSync5(nextActionsPath)) {
725
2341
  const nextActionsContent = await Bun.file(nextActionsPath).text();
726
2342
  if (nextActionsContent.includes("TODO: Replace this scaffold") || nextActionsContent.includes("bd-<downstream-task-id>")) {
727
2343
  localReasons.push("[Artifact Quality] next-actions.md still contains scaffold placeholder text. Replace with real recommendations.");
@@ -752,7 +2368,7 @@ async function verifyTask(options) {
752
2368
  aiReasons.push(`[AI Review] Required mode needs a completed Greptile approval; current verdict is ${ai.verdict}.`);
753
2369
  }
754
2370
  if (persistArtifacts && ai.rawResponse) {
755
- writeFileSync(greptileRawPath, `${ai.rawResponse}
2371
+ writeFileSync2(greptileRawPath, `${ai.rawResponse}
756
2372
  `, "utf-8");
757
2373
  }
758
2374
  } else if (!options.skipAiReview && reviewMode === "off") {
@@ -874,15 +2490,15 @@ function nextActionsIndicateRemainingScope(content) {
874
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);
875
2491
  }
876
2492
  async function hasConfiguredSourceTask(projectRoot, taskId) {
877
- return readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task !== null).catch(() => false);
2493
+ return taskData().readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task !== null).catch(() => false);
878
2494
  }
879
2495
  function resolveGithubSourceIssueId(projectRoot, taskId) {
880
- const fromRuntime = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
2496
+ const fromRuntime = loadRuntimeContextFromEnv2()?.sourceTask?.sourceIssueId;
881
2497
  if (typeof fromRuntime === "string" && isGithubSourceIssueId(fromRuntime)) {
882
2498
  return fromRuntime;
883
2499
  }
884
2500
  try {
885
- const taskConfig = readTaskConfig(projectRoot);
2501
+ const taskConfig = taskData().readTaskConfig(projectRoot);
886
2502
  const entry = taskConfig[taskId];
887
2503
  const sourceIssueId = typeof entry?.sourceIssueId === "string" ? entry.sourceIssueId : typeof entry?.source_issue_id === "string" ? entry.source_issue_id : null;
888
2504
  if (sourceIssueId && isGithubSourceIssueId(sourceIssueId)) {
@@ -953,14 +2569,15 @@ function loadGithubPullRequestCloseoutSnapshot(projectRoot, prState) {
953
2569
  "--json",
954
2570
  "state,isDraft,mergeable,mergeStateStatus,reviewDecision,title,body,statusCheckRollup"
955
2571
  ]);
2572
+ const isDraft = booleanField(view, "isDraft");
956
2573
  return {
957
- state: stringField(view, "state"),
958
- isDraft: booleanField(view, "isDraft"),
959
- mergeable: stringField(view, "mergeable"),
960
- mergeStateStatus: stringField(view, "mergeStateStatus"),
961
- reviewDecision: stringField(view, "reviewDecision"),
962
- title: stringField(view, "title"),
963
- 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")),
964
2581
  statusCheckRollup: statusCheckRollupField(view, "statusCheckRollup"),
965
2582
  reviewThreads: loadGithubReviewThreads(projectRoot, repoName, prNumber)
966
2583
  };
@@ -1035,6 +2652,9 @@ function stringField(record, key) {
1035
2652
  const value = record[key];
1036
2653
  return typeof value === "string" ? value : undefined;
1037
2654
  }
2655
+ function objectField(key, value) {
2656
+ return value === undefined ? {} : { [key]: value };
2657
+ }
1038
2658
  function booleanField(record, key) {
1039
2659
  const value = record[key];
1040
2660
  return typeof value === "boolean" ? value : undefined;
@@ -1091,7 +2711,7 @@ function isAcceptedValidationSummary(summary) {
1091
2711
  return summary.status === "skipped" && summary.total === 0 && summary.failed === 0;
1092
2712
  }
1093
2713
  async function loadReviewMode(reviewProfilePath, fallback) {
1094
- const parsed = existsSync(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
2714
+ const parsed = existsSync5(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
1095
2715
  const mode = parsed?.mode;
1096
2716
  if (mode === "off" || mode === "advisory" || mode === "required") {
1097
2717
  return mode;
@@ -1102,7 +2722,7 @@ async function loadReviewMode(reviewProfilePath, fallback) {
1102
2722
  return "advisory";
1103
2723
  }
1104
2724
  async function loadReviewProvider(reviewProfilePath, fallback) {
1105
- const parsed = existsSync(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
2725
+ const parsed = existsSync5(reviewProfilePath) ? await readJsonFile(reviewProfilePath) : null;
1106
2726
  const provider = parsed?.provider;
1107
2727
  if (typeof provider === "string" && provider.trim().length > 0) {
1108
2728
  return provider;
@@ -1111,7 +2731,7 @@ async function loadReviewProvider(reviewProfilePath, fallback) {
1111
2731
  }
1112
2732
  function resolveRepoSlug(projectRoot) {
1113
2733
  const paths = resolveHarnessPaths(projectRoot);
1114
- 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();
1115
2735
  if (!remote) {
1116
2736
  return "";
1117
2737
  }
@@ -1125,7 +2745,7 @@ function resolveRepoSlug(projectRoot) {
1125
2745
  async function runGreptileReview(options) {
1126
2746
  const reasons = [];
1127
2747
  const warnings = [];
1128
- const secrets = resolveRuntimeSecrets(process.env);
2748
+ const secrets = resolveRuntimeSecrets2(process.env);
1129
2749
  const apiKey = secrets.GREPTILE_API_KEY || "";
1130
2750
  const apiBase = secrets.GREPTILE_API_BASE || "https://api.greptile.com/mcp";
1131
2751
  const remote = secrets.GREPTILE_REMOTE || "github";
@@ -1261,7 +2881,7 @@ function writeFeedbackFile(options) {
1261
2881
  if (options.aiRawFeedback) {
1262
2882
  lines.push("## Raw Reviewer Feedback", "", "```text", options.aiRawFeedback, "```", "");
1263
2883
  }
1264
- writeFileSync(options.output, `${lines.join(`
2884
+ writeFileSync2(options.output, `${lines.join(`
1265
2885
  `)}
1266
2886
  `, "utf-8");
1267
2887
  }
@@ -1276,9 +2896,9 @@ function writeReviewStateFile(options) {
1276
2896
  local_reasons: options.localReasons,
1277
2897
  ai_reasons: options.aiReasons,
1278
2898
  ai_warnings: options.aiWarnings,
1279
- updated_at: nowIso()
2899
+ updated_at: nowIso2()
1280
2900
  };
1281
- writeFileSync(options.output, `${JSON.stringify(payload, null, 2)}
2901
+ writeFileSync2(options.output, `${JSON.stringify(payload, null, 2)}
1282
2902
  `, "utf-8");
1283
2903
  }
1284
2904
  async function runGreptileReviewForPr(options) {
@@ -1303,7 +2923,6 @@ async function runGreptileReviewForPr(options) {
1303
2923
  taskId: options.taskId,
1304
2924
  prState: options.prState,
1305
2925
  reviewMode: options.reviewMode,
1306
- infrastructureError: undefined,
1307
2926
  pollAttempts: options.pollAttempts,
1308
2927
  pollIntervalMs: options.pollIntervalMs
1309
2928
  });
@@ -1431,7 +3050,7 @@ async function runGreptileReviewForPr(options) {
1431
3050
  });
1432
3051
  const actionableComments = filterActionableGreptileComments(commentsPayload.comments || []);
1433
3052
  const reviewBody = reviewDetails.codeReview?.body || "";
1434
- const score = parseGreptileScore(reviewBody);
3053
+ const score = mg().parseGreptileScore(reviewBody);
1435
3054
  const feedback = [
1436
3055
  `## ${options.prState.repoLabel || repoName} PR Review`,
1437
3056
  "",
@@ -1439,7 +3058,7 @@ async function runGreptileReviewForPr(options) {
1439
3058
  `- Review ID: ${selectedReview.id}`,
1440
3059
  `- Status: ${selectedReview.status}`,
1441
3060
  "",
1442
- reviewBody ? stripHtml(reviewBody).trim() : "Greptile completed without summary body."
3061
+ reviewBody ? mg().stripHtml(reviewBody).trim() : "Greptile completed without summary body."
1443
3062
  ].filter(Boolean).join(`
1444
3063
  `);
1445
3064
  if (actionableComments.length > 0) {
@@ -1460,7 +3079,7 @@ async function runGreptileReviewForPr(options) {
1460
3079
  }
1461
3080
  };
1462
3081
  }
1463
- 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, " ");
1464
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)) {
1465
3084
  reasons.push(`[AI Review] ${repoName}#${prNumber} summary indicates the PR is not safe to merge.`);
1466
3085
  return {
@@ -1508,7 +3127,7 @@ async function runGreptileReviewForPr(options) {
1508
3127
  status: selectedReview.status
1509
3128
  }]
1510
3129
  });
1511
- strictGate = evaluateStrictPrMergeGate(strictEvidence);
3130
+ strictGate = mg().evaluateGate(strictEvidence);
1512
3131
  } catch (error) {
1513
3132
  reasons.push(`[AI Review] Strict Greptile evidence collection failed for ${repoName}#${prNumber}: ${error instanceof Error ? error.message : String(error)}`);
1514
3133
  return {
@@ -1635,7 +3254,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
1635
3254
  fallbackReview?.html_url ? `- Review: ${fallbackReview.html_url}` : "",
1636
3255
  fallbackReview?.state ? `- Status: ${fallbackReview.state}` : "",
1637
3256
  "",
1638
- 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."
1639
3258
  ].filter(Boolean).join(`
1640
3259
  `);
1641
3260
  const warnings = buildGithubGreptileFallbackWarnings(options);
@@ -1658,7 +3277,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
1658
3277
  taskId: options.taskId,
1659
3278
  prUrl
1660
3279
  });
1661
- strictGate = evaluateStrictPrMergeGate(strictEvidence);
3280
+ strictGate = mg().evaluateGate(strictEvidence);
1662
3281
  } catch (error) {
1663
3282
  return {
1664
3283
  verdict: "REJECT",
@@ -1883,7 +3502,7 @@ function loadGithubPullRequestState(projectRoot, repoName, prNumber) {
1883
3502
  ]);
1884
3503
  return {
1885
3504
  state: response.state || "",
1886
- merged: response.merged,
3505
+ ...response.merged !== undefined ? { merged: response.merged } : {},
1887
3506
  merged_at: response.merged_at ?? null
1888
3507
  };
1889
3508
  }
@@ -1901,7 +3520,7 @@ function parsePullRequestNumber(url) {
1901
3520
  return match ? Number.parseInt(match[1] || "0", 10) : 0;
1902
3521
  }
1903
3522
  function runGhJson(projectRoot, args) {
1904
- const result = runCapture(["gh", ...args], projectRoot);
3523
+ const result = runCapture2(["gh", ...args], projectRoot);
1905
3524
  if (result.exitCode !== 0) {
1906
3525
  throw new Error(result.stderr || result.stdout || `gh ${args.join(" ")} failed`);
1907
3526
  }
@@ -1912,7 +3531,7 @@ function runGhJson(projectRoot, args) {
1912
3531
  }
1913
3532
  }
1914
3533
  async function collectStrictPrEvidenceForVerifier(input) {
1915
- return collectPrReviewEvidence({
3534
+ return mg().collectEvidence({
1916
3535
  projectRoot: input.projectRoot,
1917
3536
  prUrl: input.prUrl,
1918
3537
  taskId: input.taskId,
@@ -1920,7 +3539,7 @@ async function collectStrictPrEvidenceForVerifier(input) {
1920
3539
  cycle: 0,
1921
3540
  apiSignals: input.apiSignals ?? [],
1922
3541
  command: async (args, options) => {
1923
- const result = runCapture(["gh", ...args], options?.cwd ?? input.projectRoot);
3542
+ const result = runCapture2(["gh", ...args], options?.cwd ?? input.projectRoot);
1924
3543
  return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
1925
3544
  }
1926
3545
  });
@@ -1933,11 +3552,11 @@ function deriveRepoName(projectRoot, prState) {
1933
3552
  if (prState.target === "monorepo") {
1934
3553
  return resolveRepoSlug(projectRoot);
1935
3554
  }
1936
- 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();
1937
3556
  }
1938
3557
  function resolvePrHeadSha(projectRoot, prState) {
1939
3558
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
1940
- return runCapture(["git", "-C", repoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
3559
+ return runCapture2(["git", "-C", repoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
1941
3560
  }
1942
3561
  function isGreptileGithubLogin(login) {
1943
3562
  const normalized = (login || "").toLowerCase().replace(/\[bot\]$/, "");
@@ -2077,7 +3696,7 @@ function filterActionableGithubGreptileThreads(threads) {
2077
3696
  }
2078
3697
  function resolvePrRepoRoot(projectRoot, prState) {
2079
3698
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
2080
- if (prState.target === "monorepo" && runtimeWorkspace && existsSync(resolve(runtimeWorkspace, ".git"))) {
3699
+ if (prState.target === "monorepo" && runtimeWorkspace && existsSync5(resolve4(runtimeWorkspace, ".git"))) {
2081
3700
  return runtimeWorkspace;
2082
3701
  }
2083
3702
  const paths = resolveHarnessPaths(projectRoot);
@@ -2088,10 +3707,10 @@ function isCommitAncestorOfPrHead(projectRoot, prState, reviewedCommit, headComm
2088
3707
  return false;
2089
3708
  }
2090
3709
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
2091
- 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;
2092
3711
  }
2093
3712
  function summarizeComment(input) {
2094
- const text = stripHtml(input).replace(/\s+/g, " ").trim();
3713
+ const text = mg().stripHtml(input).replace(/\s+/g, " ").trim();
2095
3714
  return text.length > 160 ? `${text.slice(0, 157)}...` : text;
2096
3715
  }
2097
3716
  function asGreptileInfrastructureWarning(reason) {
@@ -2106,7 +3725,12 @@ function isAiReviewApproved(input) {
2106
3725
  }
2107
3726
  return input.aiVerdict === "APPROVE" && input.aiReasons.length === 0;
2108
3727
  }
2109
- 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
+ });
2110
3734
 
2111
3735
  // packages/bundle-default-lifecycle/src/control-plane/completion-verification.ts
2112
3736
  var exports_completion_verification = {};
@@ -2115,43 +3739,33 @@ __export(exports_completion_verification, {
2115
3739
  formatCompletionBlockedMessage: () => formatCompletionBlockedMessage,
2116
3740
  closeCompletedTaskSource: () => closeCompletedTaskSource
2117
3741
  });
2118
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
2119
- import { resolve as resolve2 } from "path";
2120
- 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 resolve5 } from "path";
3744
+ import { safePathSegment as safePathSegment2 } from "@rig/core/safe-identifiers";
2121
3745
  import {
2122
- escapeRegExp,
3746
+ escapeRegExp as escapeRegExp2,
2123
3747
  resolveBunCli,
2124
3748
  resolveBunCliInvocation,
2125
3749
  resolveTaskScopes,
2126
3750
  resolvePolicyContent
2127
3751
  } from "@rig/hook-kit";
2128
- import { loadPolicy, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
2129
- import { gitCommit, gitMergePr, gitOpenPr, readPrMetadata as readPrMetadata2, resolveTaskBranchRef } from "@rig/runtime/control-plane/native/git-ops";
2130
- import { runStrictPrMergeGate as runStrictPrMergeGate2 } from "@rig/pr-review-plugin";
2131
- import { strictMergeHeadShaFromGate as strictMergeHeadShaFromGate2 } from "@rig/contracts";
2132
- import { changedFilesForTask, pendingFilesForTask, taskArtifacts, taskValidate as taskValidate2 } from "@rig/runtime/control-plane/native/task-ops";
2133
- import { currentTaskId } from "@rig/runtime/control-plane/native/task-state";
2134
- import { resolveHarnessPaths as resolveHarnessPaths2, runCapture as runCapture2 } from "@rig/runtime/control-plane/native/utils";
2135
- import { readSourceAwareTaskStatus } from "@rig/runtime/control-plane/tasks/source-aware-task-config-source";
2136
- import {
2137
- buildTaskRunLifecycleComment,
2138
- updateConfiguredTaskSourceTask
2139
- } from "@rig/runtime/control-plane/tasks/source-lifecycle";
2140
- import { buildPluginHostContext as buildPluginHostContext2 } 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 } from "@rig/core/plugin-host-context";
2141
3755
  async function closeCompletedTaskSource(projectRoot, taskId) {
2142
- const comment = buildTaskRunLifecycleComment({
3756
+ const comment = taskData().buildTaskRunLifecycleComment({
2143
3757
  runId: process.env.RIG_SERVER_RUN_ID || taskId,
2144
3758
  status: "closed",
2145
3759
  summary: "Rig completion verification approved and closed this task.",
2146
- runtimeWorkspace: process.env.RIG_TASK_WORKSPACE,
2147
- logsDir: process.env.RIG_LOGS_DIR,
2148
- 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 } : {}
2149
3763
  });
2150
- const result = await updateConfiguredTaskSourceTask(projectRoot, {
3764
+ const result = await taskData().updateConfiguredTaskSourceTask(projectRoot, {
2151
3765
  taskId,
2152
3766
  update: { status: "closed", comment }
2153
3767
  });
2154
- const status = result.status ?? await readSourceAwareTaskStatus(projectRoot, result.taskId);
3768
+ const status = result.status ?? await taskData().readSourceAwareTaskStatus(projectRoot, result.taskId);
2155
3769
  if (!result.updated && status == null) {
2156
3770
  return {
2157
3771
  ok: true,
@@ -2171,7 +3785,7 @@ function isClosedStatus(status) {
2171
3785
  }
2172
3786
  async function runCompletionVerificationGate(projectRoot) {
2173
3787
  seedPolicyFromContent(resolvePolicyContent(projectRoot));
2174
- const taskId = currentTaskId(projectRoot);
3788
+ const taskId = taskData().currentTaskId(projectRoot);
2175
3789
  if (!taskId) {
2176
3790
  return { ok: true };
2177
3791
  }
@@ -2180,7 +3794,7 @@ async function runCompletionVerificationGate(projectRoot) {
2180
3794
  let sourceCloseoutAllowed = false;
2181
3795
  console.log(`=== Completion Verification: ${taskId} ===`);
2182
3796
  const scopes = await resolveTaskScopes(projectRoot, taskId);
2183
- const taskChangedFiles = changedFilesForTask(projectRoot, taskId, true);
3797
+ const taskChangedFiles = taskData().changedFilesForTask(projectRoot, taskId, true);
2184
3798
  const sourceInArtifacts = taskChangedFiles.filter((file) => /^artifacts\//.test(file) && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file));
2185
3799
  if (sourceInArtifacts.length > 0) {
2186
3800
  console.log(`
@@ -2193,13 +3807,13 @@ async function runCompletionVerificationGate(projectRoot) {
2193
3807
  }
2194
3808
  failed = true;
2195
3809
  }
2196
- const pluginHostCtx = await buildPluginHostContext2(projectRoot).catch((error) => {
3810
+ const pluginHostCtx = await buildPluginHostContext(projectRoot).catch((error) => {
2197
3811
  console.warn(`[completion-verification] plugin host unavailable for validators: ${error instanceof Error ? error.message : String(error)}`);
2198
3812
  return null;
2199
3813
  });
2200
3814
  console.log(`
2201
3815
  [1/3] Task validation...`);
2202
- if (!await taskValidate2(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined)) {
3816
+ if (!await taskData().taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined)) {
2203
3817
  console.log(`FAIL: Validation failed for ${taskId}`);
2204
3818
  failed = true;
2205
3819
  } else {
@@ -2212,7 +3826,7 @@ async function runCompletionVerificationGate(projectRoot) {
2212
3826
  failed = true;
2213
3827
  }
2214
3828
  }
2215
- taskArtifacts(projectRoot, taskId);
3829
+ taskData().taskArtifacts(projectRoot, taskId);
2216
3830
  const policy = loadPolicy(projectRoot);
2217
3831
  const openPrEnabled = policy.completion.checks.includes("open-pr");
2218
3832
  const autoMergeEnabled = policy.completion.checks.includes("auto-merge");
@@ -2239,7 +3853,7 @@ async function runCompletionVerificationGate(projectRoot) {
2239
3853
  } else {
2240
3854
  console.log("Verifier preflight: skipped (earlier checks failed)");
2241
3855
  }
2242
- const pendingTaskChangedFiles = pendingFilesForTask(projectRoot, taskId, true);
3856
+ const pendingTaskChangedFiles = taskData().pendingFilesForTask(projectRoot, taskId, true);
2243
3857
  const hasLocalChanges = pendingTaskChangedFiles.length > 0;
2244
3858
  console.log(`
2245
3859
  [post] Auto-committing task changes...`);
@@ -2312,14 +3926,15 @@ async function runCompletionVerificationGate(projectRoot) {
2312
3926
  console.log(`
2313
3927
  [post] Auto-merge...`);
2314
3928
  try {
2315
- const prs = readPrMetadata2(projectRoot, taskId);
3929
+ const prs = readPrMetadata(projectRoot, taskId);
2316
3930
  if (prs.length === 0) {
2317
3931
  console.log("Auto-merge: skipped (no PR metadata found)");
2318
3932
  } else {
3933
+ const mergeGate = await resolvePrMergeGateService(projectRoot);
2319
3934
  let cycle = 0;
2320
3935
  for (const pr of prs) {
2321
3936
  cycle += 1;
2322
- const gate = await runStrictPrMergeGate2({
3937
+ const gate = await mergeGate.runGate({
2323
3938
  projectRoot,
2324
3939
  prUrl: pr.url,
2325
3940
  taskId,
@@ -2327,7 +3942,7 @@ async function runCompletionVerificationGate(projectRoot) {
2327
3942
  cycle,
2328
3943
  final: true,
2329
3944
  command: async (args, options) => {
2330
- const result = runCapture2(["gh", ...args], options?.cwd ?? projectRoot);
3945
+ const result = runCapture3(["gh", ...args], options?.cwd ?? projectRoot);
2331
3946
  return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
2332
3947
  }
2333
3948
  });
@@ -2344,7 +3959,7 @@ async function runCompletionVerificationGate(projectRoot) {
2344
3959
  pr,
2345
3960
  method: "squash",
2346
3961
  deleteBranch: true,
2347
- matchHeadCommit: strictMergeHeadShaFromGate2(gate, pr.url)
3962
+ matchHeadCommit: mergeGate.resolveHeadSha({ result: gate, prUrl: pr.url })
2348
3963
  });
2349
3964
  if (mergeResult.status === "merged" || mergeResult.status === "already-merged") {
2350
3965
  console.log(`OK: PR merge confirmed (${pr.repoLabel}): ${pr.url}`);
@@ -2364,9 +3979,9 @@ async function runCompletionVerificationGate(projectRoot) {
2364
3979
  console.log(`
2365
3980
  [post] Auto-merge: skipped (not in policy completion.checks)`);
2366
3981
  }
2367
- const artifactDir = resolve2(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
2368
- mkdirSync2(artifactDir, { recursive: true });
2369
- writeFileSync2(resolve2(artifactDir, "review-status.txt"), failed ? `REJECTED
3982
+ const artifactDir = resolve5(paths.artifactsDir, safePathSegment2(taskId, { fallback: "task", maxLength: 96 }));
3983
+ mkdirSync3(artifactDir, { recursive: true });
3984
+ writeFileSync3(resolve5(artifactDir, "review-status.txt"), failed ? `REJECTED
2370
3985
  ` : `APPROVED
2371
3986
  `, "utf-8");
2372
3987
  if (!failed) {
@@ -2416,8 +4031,8 @@ async function runBunTool(args, cwd) {
2416
4031
  };
2417
4032
  }
2418
4033
  async function runProtoQualityGate(monorepoRoot) {
2419
- const protosDir = resolve2(monorepoRoot, "packages", "protos");
2420
- if (!existsSync2(protosDir)) {
4034
+ const protosDir = resolve5(monorepoRoot, "packages", "protos");
4035
+ if (!existsSync6(protosDir)) {
2421
4036
  console.log(`FAIL: Proto workspace not found at ${protosDir}`);
2422
4037
  return false;
2423
4038
  }
@@ -2444,7 +4059,7 @@ async function runProtoQualityGate(monorepoRoot) {
2444
4059
  console.log(generate.stderr || generate.stdout);
2445
4060
  ok = false;
2446
4061
  } else {
2447
- const drift = runCapture2(["git", "-C", protosDir, "status", "--porcelain", "--", "gen/ts"], monorepoRoot);
4062
+ const drift = runCapture3(["git", "-C", protosDir, "status", "--porcelain", "--", "gen/ts"], monorepoRoot);
2448
4063
  if (drift.exitCode !== 0) {
2449
4064
  console.log("FAIL: Could not inspect generated proto drift");
2450
4065
  console.log(drift.stderr || drift.stdout);
@@ -2465,12 +4080,12 @@ async function runProtoQualityGate(monorepoRoot) {
2465
4080
  } else {
2466
4081
  console.log("OK: Generated TypeScript compiles");
2467
4082
  }
2468
- const workflowPath = resolve2(monorepoRoot, ".github", "workflows", "pull-request-gate.yml");
2469
- if (!existsSync2(workflowPath)) {
4083
+ const workflowPath = resolve5(monorepoRoot, ".github", "workflows", "pull-request-gate.yml");
4084
+ if (!existsSync6(workflowPath)) {
2470
4085
  console.log(`FAIL: Missing workflow gate file at ${workflowPath}`);
2471
4086
  ok = false;
2472
4087
  } else {
2473
- const workflow = readFileSync(workflowPath, "utf-8");
4088
+ const workflow = readFileSync4(workflowPath, "utf-8");
2474
4089
  if (workflow.includes("if: false && needs.detect.outputs.protos_changed == 'true'")) {
2475
4090
  console.log("FAIL: Proto quality CI gate is disabled in pull-request-gate.yml");
2476
4091
  ok = false;
@@ -2481,13 +4096,13 @@ async function runProtoQualityGate(monorepoRoot) {
2481
4096
  return ok;
2482
4097
  }
2483
4098
  function repoHasRemoteRelevantCommits(projectRoot, repoRoot) {
2484
- const unpushed = runCapture2(["git", "-C", repoRoot, "log", "@{u}..HEAD", "--oneline"], projectRoot);
4099
+ const unpushed = runCapture3(["git", "-C", repoRoot, "log", "@{u}..HEAD", "--oneline"], projectRoot);
2485
4100
  if (unpushed.exitCode === 0 && unpushed.stdout.trim().length > 0)
2486
4101
  return true;
2487
4102
  if (unpushed.exitCode !== 0) {
2488
- 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);
2489
4104
  if (branch.exitCode === 0 && branch.stdout.trim()) {
2490
- 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);
2491
4106
  if (remote.exitCode !== 0)
2492
4107
  return true;
2493
4108
  }
@@ -2496,10 +4111,10 @@ function repoHasRemoteRelevantCommits(projectRoot, repoRoot) {
2496
4111
  }
2497
4112
  function repoHasPublishedTaskBranch(projectRoot, repoRoot, taskId) {
2498
4113
  const branchRef = resolveTaskBranchRef(projectRoot, taskId);
2499
- 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;
2500
4115
  }
2501
4116
  async function readJsonFileIfPresent(path) {
2502
- if (!existsSync2(path)) {
4117
+ if (!existsSync6(path)) {
2503
4118
  return null;
2504
4119
  }
2505
4120
  try {
@@ -2510,9 +4125,9 @@ async function readJsonFileIfPresent(path) {
2510
4125
  }
2511
4126
  async function recordVerifierFailure(projectRoot, taskId, paths) {
2512
4127
  const failedApproachesPath = paths.failedApproachesPath;
2513
- const artifactDir = resolve2(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
2514
- const reviewStatePath = resolve2(artifactDir, "review-state.json");
2515
- const reviewFeedbackPath = resolve2(artifactDir, "review-feedback.md");
4128
+ const artifactDir = resolve5(paths.artifactsDir, safePathSegment2(taskId, { fallback: "task", maxLength: 96 }));
4129
+ const reviewStatePath = resolve5(artifactDir, "review-state.json");
4130
+ const reviewFeedbackPath = resolve5(artifactDir, "review-feedback.md");
2516
4131
  let summary = "Verifier rejected completion. Read review-feedback.md for required fixes.";
2517
4132
  const parsedReviewState = await readJsonFileIfPresent(reviewStatePath);
2518
4133
  if (parsedReviewState) {
@@ -2522,12 +4137,12 @@ async function recordVerifierFailure(projectRoot, taskId, paths) {
2522
4137
  }
2523
4138
  }
2524
4139
  let attempts = 1;
2525
- if (existsSync2(failedApproachesPath)) {
2526
- const content = readFileSync(failedApproachesPath, "utf-8");
2527
- 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;
2528
4143
  } else {
2529
- mkdirSync2(resolve2(failedApproachesPath, ".."), { recursive: true });
2530
- writeFileSync2(failedApproachesPath, `# Failed Approaches
4144
+ mkdirSync3(resolve5(failedApproachesPath, ".."), { recursive: true });
4145
+ writeFileSync3(failedApproachesPath, `# Failed Approaches
2531
4146
 
2532
4147
  `, "utf-8");
2533
4148
  }
@@ -2551,7 +4166,7 @@ async function recordTaskRepoCommits(projectRoot, taskId, paths) {
2551
4166
  statePaths.add(resolveHarnessPaths2(hostProjectRoot).taskRepoCommitsPath);
2552
4167
  }
2553
4168
  const repos = {};
2554
- 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();
2555
4170
  if (monoHead) {
2556
4171
  repos["monorepo"] = monoHead;
2557
4172
  }
@@ -2565,13 +4180,17 @@ async function recordTaskRepoCommits(projectRoot, taskId, paths) {
2565
4180
  recorded_at: new Date().toISOString(),
2566
4181
  repos
2567
4182
  };
2568
- mkdirSync2(resolve2(statePath, ".."), { recursive: true });
2569
- writeFileSync2(statePath, `${JSON.stringify(state, null, 2)}
4183
+ mkdirSync3(resolve5(statePath, ".."), { recursive: true });
4184
+ writeFileSync3(statePath, `${JSON.stringify(state, null, 2)}
2570
4185
  `, "utf-8");
2571
4186
  }
2572
4187
  }
2573
4188
  var init_completion_verification = __esm(() => {
4189
+ init_policy();
4190
+ init_git_ops();
4191
+ init_pr_merge_gate_cap();
2574
4192
  init_verifier();
4193
+ init_task_data();
2575
4194
  });
2576
4195
 
2577
4196
  // packages/bundle-default-lifecycle/src/control-plane/task-verify.ts
@@ -2579,9 +4198,8 @@ var exports_task_verify = {};
2579
4198
  __export(exports_task_verify, {
2580
4199
  taskVerify: () => taskVerify
2581
4200
  });
2582
- import { currentTaskId as currentTaskId2 } from "@rig/runtime/control-plane/native/task-state";
2583
4201
  async function taskVerify(projectRoot, taskId) {
2584
- const activeTask = taskId || currentTaskId2(projectRoot);
4202
+ const activeTask = taskId || taskData().currentTaskId(projectRoot);
2585
4203
  if (!activeTask) {
2586
4204
  throw new Error("No active task.");
2587
4205
  }
@@ -2606,42 +4224,28 @@ async function taskVerify(projectRoot, taskId) {
2606
4224
  return true;
2607
4225
  }
2608
4226
  var init_task_verify = __esm(() => {
4227
+ init_task_data();
2609
4228
  init_verifier();
2610
4229
  });
2611
4230
 
2612
4231
  // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
2613
- import { resolve as resolve3 } from "path";
2614
- import { loadConfig as loadConfig2 } from "@rig/core/load-config";
2615
- import { resolvePluginHost } from "@rig/core/project-plugins";
2616
- import { createDefaultKernel } from "@rig/kernel/default-kernel";
2617
- import { buildPluginHostContext as buildPluginHostContext3 } from "@rig/runtime/control-plane/plugin-host-context";
2618
-
2619
- // packages/bundle-default-lifecycle/src/native/in-process-closeout.ts
2620
- init_pr_automation();
4232
+ import { resolve as resolve6 } from "path";
2621
4233
  import { loadConfig } from "@rig/core/load-config";
2622
- import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
2623
- import { taskValidate } from "@rig/runtime/control-plane/native/task-ops";
2624
- var CLOSEOUT_PHASES = new Set([
2625
- "queued",
2626
- "commit",
2627
- "push",
2628
- "pr-review-merge",
2629
- "pr-opened",
2630
- "merge",
2631
- "close-source",
2632
- "completed"
2633
- ]);
4234
+ import { resolvePluginHost as resolvePluginHost2 } from "@rig/core/project-plugins";
4235
+ import { createDefaultKernel } from "@rig/kernel-seed/default-kernel";
4236
+ import { buildPluginHostContext as buildPluginHostContext2 } from "@rig/core/plugin-host-context";
2634
4237
 
4238
+ // packages/bundle-default-lifecycle/src/native/in-process-closeout.ts
2635
4239
  class CloseoutValidationError extends Error {
2636
4240
  name = "CloseoutValidationError";
2637
4241
  }
2638
4242
 
2639
4243
  // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
2640
- import { taskValidate as taskValidate3 } from "@rig/runtime/control-plane/native/task-ops";
4244
+ init_task_data();
2641
4245
 
2642
4246
  // packages/bundle-default-lifecycle/src/defaultPipeline.ts
2643
- import { resolveKernelStages } from "@rig/kernel/resolver";
2644
- import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
4247
+ import { createDefaultKernelPlugin } from "@rig/kernel-seed/default-kernel";
4248
+ import { resolveKernelStages } from "@rig/kernel-seed/resolver";
2645
4249
 
2646
4250
  // packages/bundle-default-lifecycle/src/stages/types.ts
2647
4251
  function defineDefaultLifecycleStage(input) {
@@ -2677,6 +4281,9 @@ async function runCommitStage(input) {
2677
4281
  }
2678
4282
 
2679
4283
  // packages/bundle-default-lifecycle/src/stages/isolation.ts
4284
+ import { ISOLATION_BACKEND } from "@rig/contracts";
4285
+ import { defineCapability as defineCapability3 } from "@rig/core/capability";
4286
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
2680
4287
  var isolationStage = defineDefaultLifecycleStage({
2681
4288
  id: "isolation",
2682
4289
  kind: "transform",
@@ -2700,8 +4307,9 @@ var mergeGateStage = defineDefaultLifecycleStage({
2700
4307
  calls: ["runStrictPrMergeGate"]
2701
4308
  });
2702
4309
  async function runMergeGateStage(input) {
2703
- const { runStrictPrMergeGate: runStrictPrMergeGate2 } = await import("@rig/pr-review-plugin");
2704
- return await runStrictPrMergeGate2({
4310
+ const { resolvePrMergeGateService: resolvePrMergeGateService2 } = await Promise.resolve().then(() => (init_pr_merge_gate_cap(), exports_pr_merge_gate_cap));
4311
+ const mergeGate = await resolvePrMergeGateService2(input.projectRoot);
4312
+ return await mergeGate.runGate({
2705
4313
  projectRoot: input.projectRoot,
2706
4314
  prUrl: input.prUrl,
2707
4315
  taskId: input.taskId,
@@ -2893,9 +4501,13 @@ function formatDefaultPipelineShow(input = {}) {
2893
4501
 
2894
4502
  // packages/bundle-default-lifecycle/src/plugin.ts
2895
4503
  import { definePlugin } from "@rig/core/config";
4504
+ import { defineCapability as defineCapability4 } from "@rig/core/capability";
2896
4505
  import {
2897
- COMPLETION_VERIFICATION_CAPABILITY_ID,
2898
- TASK_VERIFY_CAPABILITY_ID
4506
+ COMPLETION_VERIFICATION_CAPABILITY,
4507
+ LIFECYCLE_GIT_AGENT,
4508
+ LIFECYCLE_TOOLCHAIN_SOURCES,
4509
+ RUN_CLOSEOUT_CAPABILITY,
4510
+ TASK_VERIFY_CAPABILITY
2899
4511
  } from "@rig/contracts";
2900
4512
 
2901
4513
  // packages/bundle-default-lifecycle/src/cli.ts
@@ -2970,7 +4582,46 @@ var defaultLifecycleCliCommands = [
2970
4582
  }
2971
4583
  ];
2972
4584
 
4585
+ // packages/bundle-default-lifecycle/src/native/closeout-runners.ts
4586
+ init_github_auth_env();
4587
+ function commandEnv(env) {
4588
+ const token = resolveGitHubAuthToken(env);
4589
+ return token ? { ...env, RIG_GITHUB_TOKEN: token, GH_TOKEN: token, GITHUB_TOKEN: token } : { ...env };
4590
+ }
4591
+ function createBunCommandRunner(binary, env) {
4592
+ return async (args, options) => {
4593
+ try {
4594
+ const child = Bun.spawn([binary, ...args], {
4595
+ ...options?.cwd !== undefined ? { cwd: options.cwd } : {},
4596
+ env: commandEnv(env),
4597
+ stdin: "ignore",
4598
+ stdout: "pipe",
4599
+ stderr: "pipe"
4600
+ });
4601
+ const [exitCode, stdout, stderr] = await Promise.all([
4602
+ child.exited,
4603
+ new Response(child.stdout).text(),
4604
+ new Response(child.stderr).text()
4605
+ ]);
4606
+ return { exitCode, stdout, stderr };
4607
+ } catch (error) {
4608
+ return {
4609
+ exitCode: 1,
4610
+ stdout: "",
4611
+ stderr: error instanceof Error ? error.message : String(error)
4612
+ };
4613
+ }
4614
+ };
4615
+ }
4616
+ function createEnvCloseoutRunners(env = process.env) {
4617
+ return {
4618
+ command: createBunCommandRunner("gh", env),
4619
+ gitCommand: createBunCommandRunner("git", env)
4620
+ };
4621
+ }
4622
+
2973
4623
  // packages/bundle-default-lifecycle/src/plugin.ts
4624
+ init_git_ops();
2974
4625
  var DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
2975
4626
  var COMPLETION_VERIFICATION_HOOK_ID = "@rig/bundle-default-lifecycle:completion-verification";
2976
4627
  async function runCompletionGate(projectRoot) {
@@ -2994,26 +4645,85 @@ var LIFECYCLE_HOOKS = [
2994
4645
  handler: completionVerificationStopHandler
2995
4646
  }
2996
4647
  ];
4648
+ var TaskVerifyCap = defineCapability4(TASK_VERIFY_CAPABILITY);
4649
+ var CompletionVerificationCap = defineCapability4(COMPLETION_VERIFICATION_CAPABILITY);
4650
+ var RunCloseoutCap = defineCapability4(RUN_CLOSEOUT_CAPABILITY);
4651
+ var LifecycleGitAgentCap = defineCapability4(LIFECYCLE_GIT_AGENT);
4652
+ var ToolchainSourcesCap = defineCapability4(LIFECYCLE_TOOLCHAIN_SOURCES);
4653
+ var LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION = {
4654
+ hookBinaries: [
4655
+ {
4656
+ name: "inject-context",
4657
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/inject-context.ts"
4658
+ },
4659
+ {
4660
+ name: "task-runtime-start",
4661
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/task-runtime-start.ts"
4662
+ }
4663
+ ]
4664
+ };
2997
4665
  var LIFECYCLE_CAPABILITIES = [
2998
4666
  { id: "default-lifecycle.pipeline", title: "Default lifecycle pipeline", commandId: DEFAULT_PIPELINE_CLI_ID },
2999
4667
  { id: "default-lifecycle.kernel-status", title: "Default kernel status", commandId: DEFAULT_KERNEL_CLI_ID },
3000
- {
3001
- id: TASK_VERIFY_CAPABILITY_ID,
4668
+ TaskVerifyCap.provide(() => async (input) => {
4669
+ const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
4670
+ return taskVerify2(input.projectRoot, input.taskId);
4671
+ }, {
3002
4672
  title: "Task verification",
3003
- description: "Verify a task's changes (local checks + AI review) and report approval.",
3004
- run: async (input) => {
3005
- const { projectRoot, taskId } = input;
3006
- const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
3007
- return taskVerify2(projectRoot, taskId);
4673
+ description: "Verify a task's changes (local checks + AI review) and report approval."
4674
+ }),
4675
+ CompletionVerificationCap.provide(() => async (input) => runCompletionGate(input.projectRoot), {
4676
+ title: "Completion verification gate",
4677
+ description: "Run the full completion gate for the active task and report whether it passed."
4678
+ }),
4679
+ RunCloseoutCap.provide(() => async (input) => {
4680
+ const closeout = await runPipelineCloseout({ ...input, ...createEnvCloseoutRunners(process.env) });
4681
+ return closeout.result;
4682
+ }, {
4683
+ title: "Run closeout",
4684
+ description: "Run the default lifecycle closeout driver for a completed OMP run."
4685
+ }),
4686
+ LifecycleGitAgentCap.provide(() => ({
4687
+ shouldScopeGitCommit,
4688
+ gitStatus,
4689
+ gitChanged,
4690
+ gitPreflight,
4691
+ gitSyncBranch,
4692
+ gitCommit,
4693
+ gitSnapshot,
4694
+ gitOpenPr
4695
+ }), {
4696
+ title: "Lifecycle git agent commands",
4697
+ description: "Task-aware git helpers used by the provider-owned rig-agent command surface."
4698
+ }),
4699
+ ToolchainSourcesCap.provide(() => LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION, {
4700
+ title: "Lifecycle toolchain sources",
4701
+ description: "Source paths for the lifecycle control-plane hook binaries (inject-context, task-runtime-start), compiled by the isolation runtime toolchain."
4702
+ })
4703
+ ];
4704
+ async function runLifecycleHookRunner(hookId, role) {
4705
+ process.env.RIG_HOOK_ROLE = role;
4706
+ const { main } = await import("@rig/core/hook-runner");
4707
+ await main(["--plugin", DEFAULT_LIFECYCLE_PLUGIN_ID, "--hook", hookId]);
4708
+ }
4709
+ var LIFECYCLE_SEED_ENTRYPOINTS = [
4710
+ {
4711
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:completion-verification-entrypoint`,
4712
+ basename: "completion-verification",
4713
+ run: ({ basename }) => runLifecycleHookRunner(COMPLETION_VERIFICATION_HOOK_ID, basename ?? "completion-verification")
4714
+ },
4715
+ {
4716
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:inject-context-entrypoint`,
4717
+ basename: "inject-context",
4718
+ run: async () => {
4719
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/inject-context");
3008
4720
  }
3009
4721
  },
3010
4722
  {
3011
- id: COMPLETION_VERIFICATION_CAPABILITY_ID,
3012
- title: "Completion verification gate",
3013
- description: "Run the full completion gate for the active task and report whether it passed.",
3014
- run: async (input) => {
3015
- const { projectRoot } = input;
3016
- return runCompletionGate(projectRoot);
4723
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:task-runtime-start-entrypoint`,
4724
+ basename: "task-runtime-start",
4725
+ run: async () => {
4726
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/task-runtime-start");
3017
4727
  }
3018
4728
  }
3019
4729
  ];
@@ -3023,10 +4733,20 @@ function createDefaultLifecyclePlugin(stages = {}) {
3023
4733
  version: "0.0.0-alpha.1",
3024
4734
  provides: [],
3025
4735
  contributes: {
3026
- stages: defaultLifecycleStages.map((stage) => stages[stage.id] ? { ...stage, run: stages[stage.id] } : stage),
4736
+ stages: defaultLifecycleStages.map((stage) => {
4737
+ const run = Object.prototype.hasOwnProperty.call(stages, stage.id) ? stages[stage.id] : undefined;
4738
+ return run ? { ...stage, run } : stage;
4739
+ }),
3027
4740
  hooks: LIFECYCLE_HOOKS,
3028
4741
  capabilities: LIFECYCLE_CAPABILITIES,
3029
- cliCommands: defaultLifecycleCliCommands
4742
+ cliCommands: defaultLifecycleCliCommands,
4743
+ seedEntrypoints: LIFECYCLE_SEED_ENTRYPOINTS,
4744
+ config: {
4745
+ defaults: () => ({
4746
+ merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },
4747
+ automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 }
4748
+ })
4749
+ }
3030
4750
  }
3031
4751
  });
3032
4752
  }
@@ -3049,18 +4769,18 @@ function closeoutOutcome(status) {
3049
4769
  }
3050
4770
  }
3051
4771
  async function loadRigAutomationConfig(projectRoot) {
3052
- return await loadConfig2(projectRoot);
4772
+ return await loadConfig(projectRoot);
3053
4773
  }
3054
4774
  async function runRigProjectValidation({ projectRoot, taskId }) {
3055
- const pluginHostCtx = await buildPluginHostContext3(projectRoot);
3056
- return taskValidate3(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
4775
+ const pluginHostCtx = await buildPluginHostContext2(projectRoot);
4776
+ return taskData().taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
3057
4777
  }
3058
4778
  function shouldAttemptRigMerge2(config) {
3059
4779
  const mode = config.merge?.mode;
3060
4780
  return mode !== "off" && mode !== "pr-ready";
3061
4781
  }
3062
4782
  async function loadPluginStageContributions(projectRoot) {
3063
- const { host } = await resolvePluginHost(projectRoot);
4783
+ const { host } = await resolvePluginHost2(projectRoot);
3064
4784
  return { executors: host.listStageExecutors(), mutations: host.listStageMutations() };
3065
4785
  }
3066
4786
  async function runPipelineCloseout(input) {
@@ -3082,7 +4802,7 @@ async function runPipelineCloseout(input) {
3082
4802
  };
3083
4803
  const shouldMerge = shouldAttemptRigMerge2(effectiveConfig);
3084
4804
  const workspace = input.workspace;
3085
- const artifactRoot = input.artifactRoot ?? resolve3(input.projectRoot, "artifacts", taskId);
4805
+ const artifactRoot = input.artifactRoot ?? resolve6(input.projectRoot, "artifacts", taskId);
3086
4806
  const journal = async (phase, status, detail) => {
3087
4807
  await input.journalPhase(phase, closeoutOutcome(status), detail ?? null);
3088
4808
  };