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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/src/cli.d.ts +1 -7
  2. package/dist/src/cli.js +5 -2
  3. package/dist/src/control-plane/completion-verification.js +1591 -118
  4. package/dist/src/control-plane/hooks/inject-context.d.ts +2 -0
  5. package/dist/src/control-plane/hooks/inject-context.js +175 -0
  6. package/dist/src/control-plane/hooks/shared.d.ts +11 -0
  7. package/dist/src/control-plane/hooks/shared.js +44 -0
  8. package/dist/src/control-plane/hooks/submodule-branch.d.ts +2 -0
  9. package/dist/src/control-plane/hooks/submodule-branch.js +432 -0
  10. package/dist/src/control-plane/hooks/task-runtime-start.d.ts +2 -0
  11. package/dist/src/control-plane/hooks/task-runtime-start.js +429 -0
  12. package/dist/src/control-plane/materialize-task-config.d.ts +29 -0
  13. package/dist/src/control-plane/materialize-task-config.js +95 -0
  14. package/dist/src/control-plane/native/git-ops.d.ts +67 -0
  15. package/dist/src/control-plane/native/git-ops.js +1390 -0
  16. package/dist/src/control-plane/policy.d.ts +3 -0
  17. package/dist/src/control-plane/policy.js +226 -0
  18. package/dist/src/control-plane/pr-automation.d.ts +2 -0
  19. package/dist/src/control-plane/pr-automation.js +26 -16
  20. package/dist/src/control-plane/pr-merge-gate-cap.d.ts +10 -0
  21. package/dist/src/control-plane/pr-merge-gate-cap.js +13 -0
  22. package/dist/src/control-plane/task-data.d.ts +13 -0
  23. package/dist/src/control-plane/task-data.js +12 -0
  24. package/dist/src/control-plane/task-verify.js +131 -59
  25. package/dist/src/control-plane/verifier.d.ts +1 -3
  26. package/dist/src/control-plane/verifier.js +133 -57
  27. package/dist/src/defaultPipeline.d.ts +1 -1
  28. package/dist/src/defaultPipeline.js +5 -2
  29. package/dist/src/index.d.ts +0 -2
  30. package/dist/src/index.js +1908 -290
  31. package/dist/src/native/closeout-runners.js +22 -2
  32. package/dist/src/native/github-auth-env.d.ts +2 -0
  33. package/dist/src/native/github-auth-env.js +25 -0
  34. package/dist/src/native/host-git.d.ts +6 -0
  35. package/dist/src/native/host-git.js +62 -0
  36. package/dist/src/native/in-process-closeout.d.ts +1 -3
  37. package/dist/src/native/in-process-closeout.js +0 -794
  38. package/dist/src/pipelineCloseout.js +1905 -185
  39. package/dist/src/plugin.js +2843 -145
  40. package/dist/src/stages/auto-merge.js +28 -16
  41. package/dist/src/stages/commit.js +28 -16
  42. package/dist/src/stages/isolation.d.ts +1 -1
  43. package/dist/src/stages/isolation.js +5 -3
  44. package/dist/src/stages/merge-gate.js +35 -3
  45. package/dist/src/stages/open-pr.js +28 -16
  46. package/dist/src/stages/push.js +28 -16
  47. package/dist/src/stages/source-closeout.js +28 -16
  48. package/package.json +29 -16
  49. package/dist/src/branch-naming.d.ts +0 -15
  50. package/dist/src/branch-naming.js +0 -33
  51. package/dist/src/closeoutEquivalence.d.ts +0 -37
  52. package/dist/src/closeoutEquivalence.js +0 -78
  53. package/dist/src/closeoutShadowHarness.d.ts +0 -27
  54. package/dist/src/closeoutShadowHarness.js +0 -29
package/dist/src/index.js CHANGED
@@ -16,6 +16,23 @@ 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/pr-merge-gate-cap.ts
20
+ var exports_pr_merge_gate_cap = {};
21
+ __export(exports_pr_merge_gate_cap, {
22
+ resolvePrMergeGateService: () => resolvePrMergeGateService
23
+ });
24
+ import { PR_MERGE_GATE } from "@rig/contracts";
25
+ import { defineCapability } from "@rig/core/capability";
26
+ import { resolvePluginHost } from "@rig/core/project-plugins";
27
+ async function resolvePrMergeGateService(projectRoot) {
28
+ const { host } = await resolvePluginHost(projectRoot);
29
+ return PrMergeGateCap.require(host);
30
+ }
31
+ var PrMergeGateCap;
32
+ var init_pr_merge_gate_cap = __esm(() => {
33
+ PrMergeGateCap = defineCapability(PR_MERGE_GATE);
34
+ });
35
+
19
36
  // packages/bundle-default-lifecycle/src/control-plane/pr-automation.ts
20
37
  var exports_pr_automation = {};
21
38
  __export(exports_pr_automation, {
@@ -33,11 +50,7 @@ __export(exports_pr_automation, {
33
50
  buildPrAutomationBody: () => buildPrAutomationBody,
34
51
  UPLOADED_SNAPSHOT_PR_MARKER: () => UPLOADED_SNAPSHOT_PR_MARKER
35
52
  });
36
- import { assertSafeGitBranchName } from "@rig/shared/safe-identifiers";
37
- import { runStrictPrMergeGate } from "@rig/pr-review-plugin";
38
- import {
39
- strictMergeHeadShaFromGate
40
- } from "@rig/contracts";
53
+ import { assertSafeGitBranchName } from "@rig/core/safe-identifiers";
41
54
  function positiveInt(value, fallback) {
42
55
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
43
56
  }
@@ -340,7 +353,7 @@ async function commitRunChanges(input) {
340
353
  async function closeIssueAfterMergedPr(input) {
341
354
  await input.updateTaskSource(input.projectRoot, {
342
355
  taskId: input.taskId,
343
- sourceTask: input.sourceTask,
356
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
344
357
  update: {
345
358
  status: "closed",
346
359
  comment: [
@@ -386,11 +399,12 @@ async function runRepoDefaultMerge(input) {
386
399
  if (merge.mode === "off")
387
400
  return;
388
401
  const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
389
- const matchHeadSha = strictMergeHeadShaFromGate(input.strictGate, input.prUrl, requireGreptile);
402
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot ?? input.cwd ?? process.cwd());
403
+ const matchHeadSha = mergeGate.resolveHeadSha({ result: input.strictGate, prUrl: input.prUrl, requireGreptile });
390
404
  const method = merge.method ?? "repo-default";
391
405
  const args = ["pr", "merge", input.prUrl];
392
406
  if (method === "repo-default") {
393
- args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, cwd: input.cwd }));
407
+ args.push(await resolveRepoDefaultMergeFlag({ prUrl: input.prUrl, command: input.command, ...input.cwd !== undefined ? { cwd: input.cwd } : {} }));
394
408
  } else {
395
409
  args.push(`--${method}`);
396
410
  }
@@ -467,6 +481,7 @@ async function syncBranchAfterPrFeedback(input) {
467
481
  }
468
482
  async function runPrAutomation(input) {
469
483
  const branch = assertSafeGitBranchName(input.branch, "PR branch");
484
+ const mergeGate = await resolvePrMergeGateService(input.projectRoot);
470
485
  const prConfig = input.config?.pr ?? {};
471
486
  const requireGreptile = (input.config?.review?.provider ?? "greptile") === "greptile";
472
487
  if (prConfig.mode === "off" || prConfig.mode === "ask") {
@@ -476,7 +491,7 @@ async function runPrAutomation(input) {
476
491
  taskId: input.taskId,
477
492
  runId: input.runId,
478
493
  summary: input.sourceTask?.title ? `Rig completed: ${input.sourceTask.title}` : null,
479
- uploadedSnapshot: input.uploadedSnapshot
494
+ ...input.uploadedSnapshot !== undefined ? { uploadedSnapshot: input.uploadedSnapshot } : {}
480
495
  });
481
496
  if (input.gitCommand) {
482
497
  await pushBranchSyncedWithOrigin({ projectRoot: input.projectRoot, branch, gitCommand: input.gitCommand });
@@ -548,16 +563,16 @@ ${createResult.stdout ?? ""}`) : null;
548
563
  await syncBranchAfterPrFeedback({ projectRoot: input.projectRoot, taskId: input.taskId, branch, gitCommand: input.gitCommand });
549
564
  continue;
550
565
  }
551
- const gate = await runStrictPrMergeGate({
566
+ const gate = await mergeGate.runGate({
552
567
  projectRoot: input.projectRoot,
553
568
  prUrl,
554
569
  taskId: input.taskId,
555
570
  runId: input.runId,
556
571
  cycle: iteration,
557
572
  command: input.command,
558
- artifactRoot: input.artifactRoot,
573
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
559
574
  allowedFailures: input.config?.merge?.allowedFailures ?? [],
560
- greptileApi: requireGreptile ? input.greptileApi : undefined,
575
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
561
576
  requireGreptile
562
577
  });
563
578
  latestFeedback = [...gate.actionableFeedback];
@@ -587,22 +602,22 @@ ${createResult.stdout ?? ""}`) : null;
587
602
  }
588
603
  if (gate.approved) {
589
604
  pendingElapsedMs = 0;
590
- const finalGate = await runStrictPrMergeGate({
605
+ const finalGate = await mergeGate.runGate({
591
606
  projectRoot: input.projectRoot,
592
607
  prUrl,
593
608
  taskId: input.taskId,
594
609
  runId: input.runId,
595
610
  cycle: iteration,
596
611
  command: input.command,
597
- artifactRoot: input.artifactRoot,
612
+ ...input.artifactRoot !== undefined ? { artifactRoot: input.artifactRoot } : {},
598
613
  allowedFailures: input.config?.merge?.allowedFailures ?? [],
599
- greptileApi: requireGreptile ? input.greptileApi : undefined,
614
+ ...requireGreptile && input.greptileApi ? { greptileApi: input.greptileApi } : {},
600
615
  requireGreptile,
601
616
  final: true
602
617
  });
603
618
  if (finalGate.approved) {
604
619
  await input.lifecycle?.onMergeStarted?.({ prUrl });
605
- await runRepoDefaultMerge({ prUrl, config: input.config, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
620
+ await runRepoDefaultMerge({ prUrl, ...input.config !== undefined ? { config: input.config } : {}, command: input.command, cwd: input.projectRoot, strictGate: finalGate });
606
621
  await input.lifecycle?.onMerged?.({ prUrl });
607
622
  return { status: "merged", prUrl, iterations: iteration, actionableFeedback: [], merged: true };
608
623
  }
@@ -649,6 +664,7 @@ ${createResult.stdout ?? ""}`) : null;
649
664
  }
650
665
  var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->", RIG_RUNTIME_COMMIT_EXCLUDES, GREPTILE_REREVIEW_MARKER_PREFIX = "rig:greptile-rereview";
651
666
  var init_pr_automation = __esm(() => {
667
+ init_pr_merge_gate_cap();
652
668
  RIG_RUNTIME_COMMIT_EXCLUDES = [
653
669
  ".rig",
654
670
  "artifacts",
@@ -656,31 +672,1631 @@ var init_pr_automation = __esm(() => {
656
672
  ];
657
673
  });
658
674
 
659
- // packages/bundle-default-lifecycle/src/control-plane/verifier.ts
660
- import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
661
- import { resolve as resolve2 } 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";
675
+ // packages/bundle-default-lifecycle/src/control-plane/task-data.ts
676
+ import { TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
677
+ import { defineCapability as defineCapability3 } from "@rig/core/capability";
678
+ import { requireInstalledCapability } from "@rig/core/capability-loaders";
679
+ function taskData() {
680
+ return requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before running the lifecycle.");
681
+ }
682
+ var TaskDataCap;
683
+ var init_task_data = __esm(() => {
684
+ TaskDataCap = defineCapability3(TASK_DATA_SERVICE_CAPABILITY);
685
+ });
686
+
687
+ // packages/bundle-default-lifecycle/src/native/github-auth-env.ts
688
+ import { existsSync, readFileSync } from "fs";
689
+ function cleanToken(value) {
690
+ const trimmed = value?.trim() ?? "";
691
+ return trimmed.length > 0 ? trimmed : null;
692
+ }
693
+ function authStateToken(env = process.env) {
694
+ const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
695
+ if (!file || !existsSync(file))
696
+ return null;
697
+ try {
698
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
699
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
700
+ } catch {
701
+ return null;
702
+ }
703
+ }
704
+ function resolveGitHubAuthToken(env = process.env) {
705
+ return cleanToken(env.RIG_GITHUB_TOKEN) ?? cleanToken(env.GH_TOKEN) ?? cleanToken(env.GITHUB_TOKEN) ?? authStateToken(env);
706
+ }
707
+ var init_github_auth_env = () => {};
708
+
709
+ // packages/bundle-default-lifecycle/src/native/host-git.ts
710
+ import { existsSync as existsSync2 } from "fs";
711
+ import { resolve } from "path";
712
+ function isRuntimeGatewayGitPath(candidate) {
713
+ return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
714
+ }
715
+ function isRuntimeGatewayGhPath(candidate) {
716
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
717
+ }
718
+ function resolveHostGitBinary() {
719
+ const candidates = [
720
+ process.env.RIG_GIT_BIN?.trim() || "",
721
+ "/usr/bin/git",
722
+ "/opt/homebrew/bin/git",
723
+ "/usr/local/bin/git"
724
+ ];
725
+ const bunResolved = Bun.which("git");
726
+ if (bunResolved && !isRuntimeGatewayGitPath(bunResolved)) {
727
+ candidates.push(bunResolved);
728
+ }
729
+ for (const candidate of candidates) {
730
+ if (!candidate || isRuntimeGatewayGitPath(candidate)) {
731
+ continue;
732
+ }
733
+ if (existsSync2(candidate)) {
734
+ return candidate;
735
+ }
736
+ }
737
+ return "git";
738
+ }
739
+ function resolveGithubCliBinary(options = {}) {
740
+ const candidates = new Set;
741
+ const explicit = process.env.RIG_GH_BIN?.trim();
742
+ if (explicit) {
743
+ candidates.add(explicit);
744
+ }
745
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
746
+ candidates.add(candidate);
747
+ }
748
+ if (options.scanPath) {
749
+ for (const entry of (process.env.PATH || "").split(":").map((e) => e.trim()).filter(Boolean)) {
750
+ candidates.add(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 = resolve2(artifactDir, "validation-summary.json");
681
- const reviewFeedbackPath = resolve2(artifactDir, "review-feedback.md");
682
- const reviewStatePath = resolve2(artifactDir, "review-state.json");
683
- const greptileRawPath = resolve2(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 (!existsSync2(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 = resolve2(artifactDir, file);
705
- if (!existsSync2(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 = resolve2(artifactDir, "task-result.json");
710
- if (existsSync2(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 = resolve2(artifactDir, "next-actions.md");
724
- if (existsSync2(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 = existsSync2(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 = existsSync2(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 && existsSync2(resolve2(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 existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2119
- import { resolve as resolve3 } 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 = resolve3(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
2368
- mkdirSync2(artifactDir, { recursive: true });
2369
- writeFileSync2(resolve3(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 = resolve3(monorepoRoot, "packages", "protos");
2420
- if (!existsSync3(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 = resolve3(monorepoRoot, ".github", "workflows", "pull-request-gate.yml");
2469
- if (!existsSync3(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 = readFileSync2(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 (!existsSync3(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 = resolve3(paths.artifactsDir, safePathSegment(taskId, { fallback: "task", maxLength: 96 }));
2514
- const reviewStatePath = resolve3(artifactDir, "review-state.json");
2515
- const reviewFeedbackPath = resolve3(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 (existsSync3(failedApproachesPath)) {
2526
- const content = readFileSync2(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(resolve3(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(resolve3(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,107 +4224,13 @@ 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
- // packages/bundle-default-lifecycle/src/closeoutEquivalence.ts
2613
- import { existsSync, readFileSync } from "fs";
2614
- import { resolve } from "path";
2615
- var CLOSEOUT_EQUIVALENCE_ARTIFACTS = [
2616
- "review-status.txt",
2617
- "review-feedback.md",
2618
- "review-state.json",
2619
- "pr-state.json",
2620
- "git-state.txt",
2621
- "validation-summary.json"
2622
- ];
2623
- var CLOSEOUT_EQUIVALENCE_STATE_FILES = [
2624
- "task-repo-commits.json",
2625
- "failed_approaches.md"
2626
- ];
2627
- function snapshotCloseoutArtifacts(input) {
2628
- const artifactRoot = input.artifactRoot ?? resolve(input.projectRoot, "artifacts", input.taskId);
2629
- const stateRoot = input.stateRoot ?? resolve(input.projectRoot, ".rig", "state");
2630
- const entries = [
2631
- ...CLOSEOUT_EQUIVALENCE_ARTIFACTS.map((name) => {
2632
- const path = resolve(artifactRoot, name);
2633
- return [name, existsSync(path) ? readFileSync(path, "utf-8") : null];
2634
- }),
2635
- ...CLOSEOUT_EQUIVALENCE_STATE_FILES.map((name) => {
2636
- const path = resolve(stateRoot, name);
2637
- return [name, existsSync(path) ? readFileSync(path, "utf-8") : null];
2638
- })
2639
- ];
2640
- return Object.fromEntries(entries);
2641
- }
2642
- function compareCloseoutEquivalence(input) {
2643
- const diffs = [];
2644
- for (const name of [...CLOSEOUT_EQUIVALENCE_ARTIFACTS, ...CLOSEOUT_EQUIVALENCE_STATE_FILES]) {
2645
- const imperative = input.imperative.artifacts[name];
2646
- const staged = input.staged.artifacts[name];
2647
- if (imperative !== staged) {
2648
- diffs.push({ kind: "artifact", path: name, imperative, staged });
2649
- }
2650
- }
2651
- const imperativeCalls = JSON.stringify(input.imperative.taskSourceCalls.map((call) => ({
2652
- method: call.method,
2653
- taskId: call.taskId,
2654
- status: call.status ?? null,
2655
- summary: call.summary ?? null
2656
- })));
2657
- const stagedCalls = JSON.stringify(input.staged.taskSourceCalls.map((call) => ({
2658
- method: call.method,
2659
- taskId: call.taskId,
2660
- status: call.status ?? null,
2661
- summary: call.summary ?? null
2662
- })));
2663
- if (imperativeCalls !== stagedCalls) {
2664
- diffs.push({ kind: "task-source", path: "task-source-calls", imperative: imperativeCalls, staged: stagedCalls });
2665
- }
2666
- const imperativeResult = JSON.stringify({
2667
- status: input.imperative.result.status,
2668
- prUrl: input.imperative.result.prUrl ?? null,
2669
- iterations: input.imperative.result.iterations,
2670
- feedback: [...input.imperative.result.feedback]
2671
- });
2672
- const stagedResult = JSON.stringify({
2673
- status: input.staged.result.status,
2674
- prUrl: input.staged.result.prUrl ?? null,
2675
- iterations: input.staged.result.iterations,
2676
- feedback: [...input.staged.result.feedback]
2677
- });
2678
- if (imperativeResult !== stagedResult) {
2679
- diffs.push({ kind: "result", path: "closeout-result", imperative: imperativeResult, staged: stagedResult });
2680
- }
2681
- return { equivalent: diffs.length === 0, diffs };
2682
- }
2683
- // packages/bundle-default-lifecycle/src/closeoutShadowHarness.ts
2684
- function effectKey(effect) {
2685
- return effect === null ? null : JSON.stringify({ kind: effect.kind, target: effect.target, detail: effect.detail ?? null });
2686
- }
2687
- function compareCloseoutShadowRuns(input) {
2688
- const max = Math.max(input.imperative.effects.length, input.staged.effects.length);
2689
- const diffs = [];
2690
- for (let index = 0;index < max; index += 1) {
2691
- const imperative = input.imperative.effects[index] ?? null;
2692
- const staged = input.staged.effects[index] ?? null;
2693
- if (effectKey(imperative) !== effectKey(staged)) {
2694
- diffs.push({ index, imperative, staged });
2695
- }
2696
- }
2697
- return { equivalent: diffs.length === 0, diffs };
2698
- }
2699
- function assertCloseoutShadowEquivalent(input) {
2700
- const comparison = compareCloseoutShadowRuns(input);
2701
- if (!comparison.equivalent) {
2702
- const details = comparison.diffs.map((diff) => `#${diff.index}: imperative=${effectKey(diff.imperative) ?? "<missing>"} staged=${effectKey(diff.staged) ?? "<missing>"}`).join("; ");
2703
- throw new Error(`Closeout shadow side effects differ: ${details}`);
2704
- }
2705
- return comparison;
2706
- }
2707
4231
  // packages/bundle-default-lifecycle/src/defaultPipeline.ts
2708
- import { resolveKernelStages } from "@rig/kernel/resolver";
2709
- import { createDefaultKernelPlugin } from "@rig/kernel/default-kernel";
4232
+ import { createDefaultKernelPlugin } from "@rig/kernel-seed/default-kernel";
4233
+ import { resolveKernelStages } from "@rig/kernel-seed/resolver";
2710
4234
 
2711
4235
  // packages/bundle-default-lifecycle/src/stages/types.ts
2712
4236
  function defineDefaultLifecycleStage(input) {
@@ -2742,6 +4266,9 @@ async function runCommitStage(input) {
2742
4266
  }
2743
4267
 
2744
4268
  // packages/bundle-default-lifecycle/src/stages/isolation.ts
4269
+ import { ISOLATION_BACKEND } from "@rig/contracts";
4270
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
4271
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
2745
4272
  var isolationStage = defineDefaultLifecycleStage({
2746
4273
  id: "isolation",
2747
4274
  kind: "transform",
@@ -2749,8 +4276,7 @@ var isolationStage = defineDefaultLifecycleStage({
2749
4276
  calls: ["ensureAgentRuntime"]
2750
4277
  });
2751
4278
  async function runIsolationStage(input) {
2752
- const { requireIsolationBackend } = await import("@rig/runtime/control-plane/isolation-backend-port");
2753
- const backend = await requireIsolationBackend(input.projectRoot);
4279
+ const backend = await requireCapabilityForRoot(input.projectRoot, defineCapability2(ISOLATION_BACKEND), `No ISOLATION_BACKEND capability is registered for project root "${input.projectRoot}". ` + "Install @rig/isolation-plugin (it ships in the default bundle) so runtime provisioning can resolve a backend.");
2754
4280
  return await backend.ensureAgentRuntime(input);
2755
4281
  }
2756
4282
 
@@ -2773,8 +4299,9 @@ var mergeGateStage = defineDefaultLifecycleStage({
2773
4299
  calls: ["runStrictPrMergeGate"]
2774
4300
  });
2775
4301
  async function runMergeGateStage(input) {
2776
- const { runStrictPrMergeGate: runStrictPrMergeGate2 } = await import("@rig/pr-review-plugin");
2777
- return await runStrictPrMergeGate2({
4302
+ const { resolvePrMergeGateService: resolvePrMergeGateService2 } = await Promise.resolve().then(() => (init_pr_merge_gate_cap(), exports_pr_merge_gate_cap));
4303
+ const mergeGate = await resolvePrMergeGateService2(input.projectRoot);
4304
+ return await mergeGate.runGate({
2778
4305
  projectRoot: input.projectRoot,
2779
4306
  prUrl: input.prUrl,
2780
4307
  taskId: input.taskId,
@@ -2985,40 +4512,29 @@ function formatDefaultPipelineShow(input = {}) {
2985
4512
  `);
2986
4513
  }
2987
4514
  // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
2988
- import { resolve as resolve4 } from "path";
2989
- import { loadConfig as loadConfig2 } from "@rig/core/load-config";
2990
- import { resolvePluginHost } from "@rig/core/project-plugins";
2991
- import { createDefaultKernel } from "@rig/kernel/default-kernel";
2992
- import { buildPluginHostContext as buildPluginHostContext3 } from "@rig/runtime/control-plane/plugin-host-context";
2993
-
2994
- // packages/bundle-default-lifecycle/src/native/in-process-closeout.ts
2995
- init_pr_automation();
4515
+ import { resolve as resolve6 } from "path";
2996
4516
  import { loadConfig } from "@rig/core/load-config";
2997
- import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
2998
- import { taskValidate } from "@rig/runtime/control-plane/native/task-ops";
2999
- var CLOSEOUT_PHASES = new Set([
3000
- "queued",
3001
- "commit",
3002
- "push",
3003
- "pr-review-merge",
3004
- "pr-opened",
3005
- "merge",
3006
- "close-source",
3007
- "completed"
3008
- ]);
4517
+ import { resolvePluginHost as resolvePluginHost2 } from "@rig/core/project-plugins";
4518
+ import { createDefaultKernel } from "@rig/kernel-seed/default-kernel";
4519
+ import { buildPluginHostContext as buildPluginHostContext2 } from "@rig/core/plugin-host-context";
3009
4520
 
4521
+ // packages/bundle-default-lifecycle/src/native/in-process-closeout.ts
3010
4522
  class CloseoutValidationError extends Error {
3011
4523
  name = "CloseoutValidationError";
3012
4524
  }
3013
4525
 
3014
4526
  // packages/bundle-default-lifecycle/src/pipelineCloseout.ts
3015
- import { taskValidate as taskValidate3 } from "@rig/runtime/control-plane/native/task-ops";
4527
+ init_task_data();
3016
4528
 
3017
4529
  // packages/bundle-default-lifecycle/src/plugin.ts
3018
4530
  import { definePlugin } from "@rig/core/config";
4531
+ import { defineCapability as defineCapability4 } from "@rig/core/capability";
3019
4532
  import {
3020
- COMPLETION_VERIFICATION_CAPABILITY_ID,
3021
- TASK_VERIFY_CAPABILITY_ID
4533
+ COMPLETION_VERIFICATION_CAPABILITY,
4534
+ LIFECYCLE_GIT_AGENT,
4535
+ LIFECYCLE_TOOLCHAIN_SOURCES,
4536
+ RUN_CLOSEOUT_CAPABILITY,
4537
+ TASK_VERIFY_CAPABILITY
3022
4538
  } from "@rig/contracts";
3023
4539
 
3024
4540
  // packages/bundle-default-lifecycle/src/cli.ts
@@ -3093,7 +4609,46 @@ var defaultLifecycleCliCommands = [
3093
4609
  }
3094
4610
  ];
3095
4611
 
4612
+ // packages/bundle-default-lifecycle/src/native/closeout-runners.ts
4613
+ init_github_auth_env();
4614
+ function commandEnv(env) {
4615
+ const token = resolveGitHubAuthToken(env);
4616
+ return token ? { ...env, RIG_GITHUB_TOKEN: token, GH_TOKEN: token, GITHUB_TOKEN: token } : { ...env };
4617
+ }
4618
+ function createBunCommandRunner(binary, env) {
4619
+ return async (args, options) => {
4620
+ try {
4621
+ const child = Bun.spawn([binary, ...args], {
4622
+ ...options?.cwd !== undefined ? { cwd: options.cwd } : {},
4623
+ env: commandEnv(env),
4624
+ stdin: "ignore",
4625
+ stdout: "pipe",
4626
+ stderr: "pipe"
4627
+ });
4628
+ const [exitCode, stdout, stderr] = await Promise.all([
4629
+ child.exited,
4630
+ new Response(child.stdout).text(),
4631
+ new Response(child.stderr).text()
4632
+ ]);
4633
+ return { exitCode, stdout, stderr };
4634
+ } catch (error) {
4635
+ return {
4636
+ exitCode: 1,
4637
+ stdout: "",
4638
+ stderr: error instanceof Error ? error.message : String(error)
4639
+ };
4640
+ }
4641
+ };
4642
+ }
4643
+ function createEnvCloseoutRunners(env = process.env) {
4644
+ return {
4645
+ command: createBunCommandRunner("gh", env),
4646
+ gitCommand: createBunCommandRunner("git", env)
4647
+ };
4648
+ }
4649
+
3096
4650
  // packages/bundle-default-lifecycle/src/plugin.ts
4651
+ init_git_ops();
3097
4652
  var DEFAULT_LIFECYCLE_PLUGIN_ID = "@rig/bundle-default-lifecycle";
3098
4653
  var COMPLETION_VERIFICATION_HOOK_ID = "@rig/bundle-default-lifecycle:completion-verification";
3099
4654
  async function runCompletionGate(projectRoot) {
@@ -3117,26 +4672,85 @@ var LIFECYCLE_HOOKS = [
3117
4672
  handler: completionVerificationStopHandler
3118
4673
  }
3119
4674
  ];
4675
+ var TaskVerifyCap = defineCapability4(TASK_VERIFY_CAPABILITY);
4676
+ var CompletionVerificationCap = defineCapability4(COMPLETION_VERIFICATION_CAPABILITY);
4677
+ var RunCloseoutCap = defineCapability4(RUN_CLOSEOUT_CAPABILITY);
4678
+ var LifecycleGitAgentCap = defineCapability4(LIFECYCLE_GIT_AGENT);
4679
+ var ToolchainSourcesCap = defineCapability4(LIFECYCLE_TOOLCHAIN_SOURCES);
4680
+ var LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION = {
4681
+ hookBinaries: [
4682
+ {
4683
+ name: "inject-context",
4684
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/inject-context.ts"
4685
+ },
4686
+ {
4687
+ name: "task-runtime-start",
4688
+ source: "packages/bundle-default-lifecycle/src/control-plane/hooks/task-runtime-start.ts"
4689
+ }
4690
+ ]
4691
+ };
3120
4692
  var LIFECYCLE_CAPABILITIES = [
3121
4693
  { id: "default-lifecycle.pipeline", title: "Default lifecycle pipeline", commandId: DEFAULT_PIPELINE_CLI_ID },
3122
4694
  { id: "default-lifecycle.kernel-status", title: "Default kernel status", commandId: DEFAULT_KERNEL_CLI_ID },
3123
- {
3124
- id: TASK_VERIFY_CAPABILITY_ID,
4695
+ TaskVerifyCap.provide(() => async (input) => {
4696
+ const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
4697
+ return taskVerify2(input.projectRoot, input.taskId);
4698
+ }, {
3125
4699
  title: "Task verification",
3126
- description: "Verify a task's changes (local checks + AI review) and report approval.",
3127
- run: async (input) => {
3128
- const { projectRoot, taskId } = input;
3129
- const { taskVerify: taskVerify2 } = await Promise.resolve().then(() => (init_task_verify(), exports_task_verify));
3130
- return taskVerify2(projectRoot, taskId);
4700
+ description: "Verify a task's changes (local checks + AI review) and report approval."
4701
+ }),
4702
+ CompletionVerificationCap.provide(() => async (input) => runCompletionGate(input.projectRoot), {
4703
+ title: "Completion verification gate",
4704
+ description: "Run the full completion gate for the active task and report whether it passed."
4705
+ }),
4706
+ RunCloseoutCap.provide(() => async (input) => {
4707
+ const closeout = await runPipelineCloseout({ ...input, ...createEnvCloseoutRunners(process.env) });
4708
+ return closeout.result;
4709
+ }, {
4710
+ title: "Run closeout",
4711
+ description: "Run the default lifecycle closeout driver for a completed OMP run."
4712
+ }),
4713
+ LifecycleGitAgentCap.provide(() => ({
4714
+ shouldScopeGitCommit,
4715
+ gitStatus,
4716
+ gitChanged,
4717
+ gitPreflight,
4718
+ gitSyncBranch,
4719
+ gitCommit,
4720
+ gitSnapshot,
4721
+ gitOpenPr
4722
+ }), {
4723
+ title: "Lifecycle git agent commands",
4724
+ description: "Task-aware git helpers used by the provider-owned rig-agent command surface."
4725
+ }),
4726
+ ToolchainSourcesCap.provide(() => LIFECYCLE_TOOLCHAIN_SOURCE_CONTRIBUTION, {
4727
+ title: "Lifecycle toolchain sources",
4728
+ description: "Source paths for the lifecycle control-plane hook binaries (inject-context, task-runtime-start), compiled by the isolation runtime toolchain."
4729
+ })
4730
+ ];
4731
+ async function runLifecycleHookRunner(hookId, role) {
4732
+ process.env.RIG_HOOK_ROLE = role;
4733
+ const { main } = await import("@rig/core/hook-runner");
4734
+ await main(["--plugin", DEFAULT_LIFECYCLE_PLUGIN_ID, "--hook", hookId]);
4735
+ }
4736
+ var LIFECYCLE_SEED_ENTRYPOINTS = [
4737
+ {
4738
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:completion-verification-entrypoint`,
4739
+ basename: "completion-verification",
4740
+ run: ({ basename }) => runLifecycleHookRunner(COMPLETION_VERIFICATION_HOOK_ID, basename ?? "completion-verification")
4741
+ },
4742
+ {
4743
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:inject-context-entrypoint`,
4744
+ basename: "inject-context",
4745
+ run: async () => {
4746
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/inject-context");
3131
4747
  }
3132
4748
  },
3133
4749
  {
3134
- id: COMPLETION_VERIFICATION_CAPABILITY_ID,
3135
- title: "Completion verification gate",
3136
- description: "Run the full completion gate for the active task and report whether it passed.",
3137
- run: async (input) => {
3138
- const { projectRoot } = input;
3139
- return runCompletionGate(projectRoot);
4750
+ id: `${DEFAULT_LIFECYCLE_PLUGIN_ID}:task-runtime-start-entrypoint`,
4751
+ basename: "task-runtime-start",
4752
+ run: async () => {
4753
+ await import("@rig/bundle-default-lifecycle/control-plane/hooks/task-runtime-start");
3140
4754
  }
3141
4755
  }
3142
4756
  ];
@@ -3146,10 +4760,20 @@ function createDefaultLifecyclePlugin(stages = {}) {
3146
4760
  version: "0.0.0-alpha.1",
3147
4761
  provides: [],
3148
4762
  contributes: {
3149
- stages: defaultLifecycleStages.map((stage) => stages[stage.id] ? { ...stage, run: stages[stage.id] } : stage),
4763
+ stages: defaultLifecycleStages.map((stage) => {
4764
+ const run = Object.prototype.hasOwnProperty.call(stages, stage.id) ? stages[stage.id] : undefined;
4765
+ return run ? { ...stage, run } : stage;
4766
+ }),
3150
4767
  hooks: LIFECYCLE_HOOKS,
3151
4768
  capabilities: LIFECYCLE_CAPABILITIES,
3152
- cliCommands: defaultLifecycleCliCommands
4769
+ cliCommands: defaultLifecycleCliCommands,
4770
+ seedEntrypoints: LIFECYCLE_SEED_ENTRYPOINTS,
4771
+ config: {
4772
+ defaults: () => ({
4773
+ merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },
4774
+ automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 }
4775
+ })
4776
+ }
3153
4777
  }
3154
4778
  });
3155
4779
  }
@@ -3172,18 +4796,18 @@ function closeoutOutcome(status) {
3172
4796
  }
3173
4797
  }
3174
4798
  async function loadRigAutomationConfig(projectRoot) {
3175
- return await loadConfig2(projectRoot);
4799
+ return await loadConfig(projectRoot);
3176
4800
  }
3177
4801
  async function runRigProjectValidation({ projectRoot, taskId }) {
3178
- const pluginHostCtx = await buildPluginHostContext3(projectRoot);
3179
- return taskValidate3(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
4802
+ const pluginHostCtx = await buildPluginHostContext2(projectRoot);
4803
+ return taskData().taskValidate(projectRoot, taskId, pluginHostCtx?.validatorRegistry ?? undefined);
3180
4804
  }
3181
4805
  function shouldAttemptRigMerge2(config) {
3182
4806
  const mode = config.merge?.mode;
3183
4807
  return mode !== "off" && mode !== "pr-ready";
3184
4808
  }
3185
4809
  async function loadPluginStageContributions(projectRoot) {
3186
- const { host } = await resolvePluginHost(projectRoot);
4810
+ const { host } = await resolvePluginHost2(projectRoot);
3187
4811
  return { executors: host.listStageExecutors(), mutations: host.listStageMutations() };
3188
4812
  }
3189
4813
  async function runPipelineCloseout(input) {
@@ -3205,7 +4829,7 @@ async function runPipelineCloseout(input) {
3205
4829
  };
3206
4830
  const shouldMerge = shouldAttemptRigMerge2(effectiveConfig);
3207
4831
  const workspace = input.workspace;
3208
- const artifactRoot = input.artifactRoot ?? resolve4(input.projectRoot, "artifacts", taskId);
4832
+ const artifactRoot = input.artifactRoot ?? resolve6(input.projectRoot, "artifacts", taskId);
3209
4833
  const journal = async (phase, status, detail) => {
3210
4834
  await input.journalPhase(phase, closeoutOutcome(status), detail ?? null);
3211
4835
  };
@@ -3405,7 +5029,6 @@ export {
3405
5029
  verifyStage,
3406
5030
  validateStage,
3407
5031
  sourceCloseoutStage,
3408
- snapshotCloseoutArtifacts,
3409
5032
  runVerifyStage,
3410
5033
  runValidateStage,
3411
5034
  runSourceCloseoutStage,
@@ -3435,16 +5058,11 @@ export {
3435
5058
  defaultLifecycleCliCommands,
3436
5059
  defaultKernelStatusData,
3437
5060
  createDefaultLifecyclePlugin,
3438
- compareCloseoutShadowRuns,
3439
- compareCloseoutEquivalence,
3440
5061
  commitStage,
3441
5062
  autoMergeStage,
3442
- assertCloseoutShadowEquivalent,
3443
5063
  DEFAULT_PIPELINE_CLI_ID,
3444
5064
  DEFAULT_LIFECYCLE_STAGE_IDS,
3445
5065
  DEFAULT_LIFECYCLE_PLUGIN_ID,
3446
5066
  DEFAULT_KERNEL_CLI_ID,
3447
- COMPLETION_VERIFICATION_HOOK_ID,
3448
- CLOSEOUT_EQUIVALENCE_STATE_FILES,
3449
- CLOSEOUT_EQUIVALENCE_ARTIFACTS
5067
+ COMPLETION_VERIFICATION_HOOK_ID
3450
5068
  };