@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.
- package/dist/src/cli.d.ts +1 -7
- package/dist/src/cli.js +5 -2
- package/dist/src/control-plane/completion-verification.js +1591 -118
- package/dist/src/control-plane/hooks/inject-context.d.ts +2 -0
- package/dist/src/control-plane/hooks/inject-context.js +175 -0
- package/dist/src/control-plane/hooks/shared.d.ts +11 -0
- package/dist/src/control-plane/hooks/shared.js +44 -0
- package/dist/src/control-plane/hooks/submodule-branch.d.ts +2 -0
- package/dist/src/control-plane/hooks/submodule-branch.js +432 -0
- package/dist/src/control-plane/hooks/task-runtime-start.d.ts +2 -0
- package/dist/src/control-plane/hooks/task-runtime-start.js +429 -0
- package/dist/src/control-plane/materialize-task-config.d.ts +29 -0
- package/dist/src/control-plane/materialize-task-config.js +95 -0
- package/dist/src/control-plane/native/git-ops.d.ts +67 -0
- package/dist/src/control-plane/native/git-ops.js +1390 -0
- package/dist/src/control-plane/policy.d.ts +3 -0
- package/dist/src/control-plane/policy.js +226 -0
- package/dist/src/control-plane/pr-automation.d.ts +2 -0
- package/dist/src/control-plane/pr-automation.js +26 -16
- package/dist/src/control-plane/pr-merge-gate-cap.d.ts +10 -0
- package/dist/src/control-plane/pr-merge-gate-cap.js +13 -0
- package/dist/src/control-plane/task-data.d.ts +13 -0
- package/dist/src/control-plane/task-data.js +12 -0
- package/dist/src/control-plane/task-verify.js +131 -59
- package/dist/src/control-plane/verifier.d.ts +1 -3
- package/dist/src/control-plane/verifier.js +133 -57
- package/dist/src/defaultPipeline.d.ts +1 -1
- package/dist/src/defaultPipeline.js +5 -2
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +1908 -290
- package/dist/src/native/closeout-runners.js +22 -2
- package/dist/src/native/github-auth-env.d.ts +2 -0
- package/dist/src/native/github-auth-env.js +25 -0
- package/dist/src/native/host-git.d.ts +6 -0
- package/dist/src/native/host-git.js +62 -0
- package/dist/src/native/in-process-closeout.d.ts +1 -3
- package/dist/src/native/in-process-closeout.js +0 -794
- package/dist/src/pipelineCloseout.js +1905 -185
- package/dist/src/plugin.js +2843 -145
- package/dist/src/stages/auto-merge.js +28 -16
- package/dist/src/stages/commit.js +28 -16
- package/dist/src/stages/isolation.d.ts +1 -1
- package/dist/src/stages/isolation.js +5 -3
- package/dist/src/stages/merge-gate.js +35 -3
- package/dist/src/stages/open-pr.js +28 -16
- package/dist/src/stages/push.js +28 -16
- package/dist/src/stages/source-closeout.js +28 -16
- package/package.json +29 -16
- package/dist/src/branch-naming.d.ts +0 -15
- package/dist/src/branch-naming.js +0 -33
- package/dist/src/closeoutEquivalence.d.ts +0 -37
- package/dist/src/closeoutEquivalence.js +0 -78
- package/dist/src/closeoutShadowHarness.d.ts +0 -27
- 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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
660
|
-
import {
|
|
661
|
-
import {
|
|
662
|
-
import {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
680
|
-
const validationSummaryPath =
|
|
681
|
-
const reviewFeedbackPath =
|
|
682
|
-
const reviewStatePath =
|
|
683
|
-
const greptileRawPath =
|
|
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 (!
|
|
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 =
|
|
705
|
-
if (!
|
|
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 =
|
|
710
|
-
if (
|
|
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 =
|
|
724
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
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
|
|
958
|
-
isDraft
|
|
959
|
-
mergeable
|
|
960
|
-
mergeStateStatus
|
|
961
|
-
reviewDecision
|
|
962
|
-
title
|
|
963
|
-
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
2899
|
+
updated_at: nowIso2()
|
|
1280
2900
|
};
|
|
1281
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 &&
|
|
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
|
|
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
|
|
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
|
|
2119
|
-
import { resolve as
|
|
2120
|
-
import { safePathSegment } from "@rig/
|
|
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 {
|
|
2129
|
-
import {
|
|
2130
|
-
import {
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
2368
|
-
|
|
2369
|
-
|
|
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 =
|
|
2420
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2469
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 =
|
|
2514
|
-
const reviewStatePath =
|
|
2515
|
-
const reviewFeedbackPath =
|
|
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 (
|
|
2526
|
-
const content =
|
|
2527
|
-
attempts = (content.match(new RegExp(`^## ${
|
|
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
|
-
|
|
2530
|
-
|
|
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 =
|
|
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
|
-
|
|
2569
|
-
|
|
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 ||
|
|
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 {
|
|
2709
|
-
import {
|
|
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
|
|
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 {
|
|
2777
|
-
|
|
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
|
|
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 {
|
|
2998
|
-
import {
|
|
2999
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3021
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
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:
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
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) =>
|
|
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
|
|
4799
|
+
return await loadConfig(projectRoot);
|
|
3176
4800
|
}
|
|
3177
4801
|
async function runRigProjectValidation({ projectRoot, taskId }) {
|
|
3178
|
-
const pluginHostCtx = await
|
|
3179
|
-
return
|
|
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
|
|
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 ??
|
|
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
|
};
|