@h-rig/runtime 0.0.6-alpha.2 → 0.0.6-alpha.20
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/bin/rig-agent-dispatch.js +84 -313
- package/dist/bin/rig-agent.js +85 -27
- package/dist/src/control-plane/agent-wrapper.js +101 -27
- package/dist/src/control-plane/authority-files.js +12 -6
- package/dist/src/control-plane/harness-main.js +1357 -180
- package/dist/src/control-plane/hooks/completion-verification.js +1669 -329
- package/dist/src/control-plane/hooks/inject-context.js +2 -2
- package/dist/src/control-plane/hooks/submodule-branch.js +26 -3
- package/dist/src/control-plane/hooks/task-runtime-start.js +26 -3
- package/dist/src/control-plane/native/git-ops.js +134 -68
- package/dist/src/control-plane/native/harness-cli.js +1357 -180
- package/dist/src/control-plane/native/pr-automation.js +1532 -54
- package/dist/src/control-plane/native/pr-review-gate.js +1330 -0
- package/dist/src/control-plane/native/run-ops.js +35 -12
- package/dist/src/control-plane/native/task-ops.js +1274 -155
- package/dist/src/control-plane/native/validator.js +2 -2
- package/dist/src/control-plane/native/verifier.js +1274 -154
- package/dist/src/control-plane/native/workspace-ops.js +12 -6
- package/dist/src/control-plane/runtime/index.js +38 -9
- package/dist/src/control-plane/runtime/isolation/home.js +31 -6
- package/dist/src/control-plane/runtime/isolation/index.js +38 -9
- package/dist/src/control-plane/runtime/isolation/runner.js +31 -6
- package/dist/src/control-plane/runtime/isolation/shared.js +9 -6
- package/dist/src/control-plane/runtime/isolation.js +38 -9
- package/dist/src/control-plane/runtime/queue.js +38 -9
- package/dist/src/control-plane/tasks/source-aware-task-config-source.js +14 -2
- package/dist/src/control-plane/tasks/source-lifecycle.js +2 -2
- package/dist/src/index.js +27 -20
- package/dist/src/layout.js +12 -7
- package/dist/src/local-server.js +20 -14
- package/native/darwin-arm64/{bin/rig-git → rig-git} +0 -0
- package/native/darwin-arm64/rig-git.build-manifest.json +4 -0
- package/native/darwin-arm64/{bin/rig-shell → rig-shell} +0 -0
- package/native/darwin-arm64/rig-shell.build-manifest.json +4 -0
- package/native/darwin-arm64/{bin/rig-tools → rig-tools} +0 -0
- package/native/darwin-arm64/rig-tools.build-manifest.json +4 -0
- package/native/darwin-arm64/{lib/runtime-native.dylib → runtime-native.dylib} +0 -0
- package/package.json +6 -6
- package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
- package/native/darwin-arm64/manifest.json +0 -1
- package/native/linux-x64/bin/rig-git +0 -0
- package/native/linux-x64/bin/rig-shell +0 -0
- package/native/linux-x64/bin/rig-tools +0 -0
- package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
- package/native/linux-x64/lib/runtime-native.so +0 -0
- package/native/linux-x64/manifest.json +0 -1
|
@@ -2484,8 +2484,8 @@ function githubStatusFor(issue) {
|
|
|
2484
2484
|
return "open";
|
|
2485
2485
|
}
|
|
2486
2486
|
function selectedGitHubEnv() {
|
|
2487
|
-
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim()
|
|
2488
|
-
return { GH_TOKEN: token, GITHUB_TOKEN: token };
|
|
2487
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
2488
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
2489
2489
|
}
|
|
2490
2490
|
function ghSpawnOptions() {
|
|
2491
2491
|
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
@@ -1706,8 +1706,8 @@ function githubStatusFor(issue) {
|
|
|
1706
1706
|
return "open";
|
|
1707
1707
|
}
|
|
1708
1708
|
function selectedGitHubEnv() {
|
|
1709
|
-
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim()
|
|
1710
|
-
return { GH_TOKEN: token, GITHUB_TOKEN: token };
|
|
1709
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
1710
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
1711
1711
|
}
|
|
1712
1712
|
function ghSpawnOptions() {
|
|
1713
1713
|
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
@@ -4509,6 +4509,10 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
env[key] = value;
|
|
4511
4511
|
}
|
|
4512
|
+
const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
4513
|
+
if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
|
|
4514
|
+
env.GITHUB_TOKEN = rigGithubToken;
|
|
4515
|
+
}
|
|
4512
4516
|
if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
|
|
4513
4517
|
env.GITHUB_TOKEN = env.GH_TOKEN;
|
|
4514
4518
|
}
|
|
@@ -4532,6 +4536,13 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4532
4536
|
if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
|
|
4533
4537
|
env.GH_TOKEN = env.GITHUB_TOKEN;
|
|
4534
4538
|
}
|
|
4539
|
+
const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
|
|
4540
|
+
if (gitHubToken) {
|
|
4541
|
+
env.RIG_GITHUB_TOKEN = gitHubToken;
|
|
4542
|
+
env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
|
|
4543
|
+
env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
|
|
4544
|
+
applyGitHubCredentialHelperEnv(env);
|
|
4545
|
+
}
|
|
4535
4546
|
if (runtimeKnownHosts && existsSync22(runtimeKnownHosts)) {
|
|
4536
4547
|
const sshParts = [
|
|
4537
4548
|
"ssh",
|
|
@@ -4548,6 +4559,14 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4548
4559
|
}
|
|
4549
4560
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
4550
4561
|
}
|
|
4562
|
+
function applyGitHubCredentialHelperEnv(env) {
|
|
4563
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
4564
|
+
env.GIT_CONFIG_COUNT = "2";
|
|
4565
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
4566
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
4567
|
+
env.GIT_CONFIG_KEY_1 = "credential.helper";
|
|
4568
|
+
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';
|
|
4569
|
+
}
|
|
4551
4570
|
function loadPersistedRuntimeSecrets(runtimeRoot) {
|
|
4552
4571
|
if (!runtimeRoot) {
|
|
4553
4572
|
return {};
|
|
@@ -7603,7 +7622,11 @@ async function ensureAgentRuntime(options) {
|
|
|
7603
7622
|
mkdirSync21(runtime.binDir, { recursive: true });
|
|
7604
7623
|
mkdirSync21(workspaceLayout.distDir, { recursive: true });
|
|
7605
7624
|
prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
|
|
7606
|
-
|
|
7625
|
+
if (options.preserveTaskArtifacts) {
|
|
7626
|
+
console.log(`[rig-agent] Preserving runtime task artifacts for resume of ${options.taskId}.`);
|
|
7627
|
+
} else {
|
|
7628
|
+
await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
|
|
7629
|
+
}
|
|
7607
7630
|
const ctx = {
|
|
7608
7631
|
runtimeId: options.id,
|
|
7609
7632
|
taskId: options.taskId,
|
|
@@ -1706,8 +1706,8 @@ function githubStatusFor(issue) {
|
|
|
1706
1706
|
return "open";
|
|
1707
1707
|
}
|
|
1708
1708
|
function selectedGitHubEnv() {
|
|
1709
|
-
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim()
|
|
1710
|
-
return { GH_TOKEN: token, GITHUB_TOKEN: token };
|
|
1709
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
1710
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
1711
1711
|
}
|
|
1712
1712
|
function ghSpawnOptions() {
|
|
1713
1713
|
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
@@ -4509,6 +4509,10 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
env[key] = value;
|
|
4511
4511
|
}
|
|
4512
|
+
const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
4513
|
+
if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
|
|
4514
|
+
env.GITHUB_TOKEN = rigGithubToken;
|
|
4515
|
+
}
|
|
4512
4516
|
if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
|
|
4513
4517
|
env.GITHUB_TOKEN = env.GH_TOKEN;
|
|
4514
4518
|
}
|
|
@@ -4532,6 +4536,13 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4532
4536
|
if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
|
|
4533
4537
|
env.GH_TOKEN = env.GITHUB_TOKEN;
|
|
4534
4538
|
}
|
|
4539
|
+
const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
|
|
4540
|
+
if (gitHubToken) {
|
|
4541
|
+
env.RIG_GITHUB_TOKEN = gitHubToken;
|
|
4542
|
+
env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
|
|
4543
|
+
env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
|
|
4544
|
+
applyGitHubCredentialHelperEnv(env);
|
|
4545
|
+
}
|
|
4535
4546
|
if (runtimeKnownHosts && existsSync22(runtimeKnownHosts)) {
|
|
4536
4547
|
const sshParts = [
|
|
4537
4548
|
"ssh",
|
|
@@ -4548,6 +4559,14 @@ function runtimeGitEnv(projectRoot) {
|
|
|
4548
4559
|
}
|
|
4549
4560
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
4550
4561
|
}
|
|
4562
|
+
function applyGitHubCredentialHelperEnv(env) {
|
|
4563
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
4564
|
+
env.GIT_CONFIG_COUNT = "2";
|
|
4565
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
4566
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
4567
|
+
env.GIT_CONFIG_KEY_1 = "credential.helper";
|
|
4568
|
+
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';
|
|
4569
|
+
}
|
|
4551
4570
|
function loadPersistedRuntimeSecrets(runtimeRoot) {
|
|
4552
4571
|
if (!runtimeRoot) {
|
|
4553
4572
|
return {};
|
|
@@ -7603,7 +7622,11 @@ async function ensureAgentRuntime(options) {
|
|
|
7603
7622
|
mkdirSync21(runtime.binDir, { recursive: true });
|
|
7604
7623
|
mkdirSync21(workspaceLayout.distDir, { recursive: true });
|
|
7605
7624
|
prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
|
|
7606
|
-
|
|
7625
|
+
if (options.preserveTaskArtifacts) {
|
|
7626
|
+
console.log(`[rig-agent] Preserving runtime task artifacts for resume of ${options.taskId}.`);
|
|
7627
|
+
} else {
|
|
7628
|
+
await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
|
|
7629
|
+
}
|
|
7607
7630
|
const ctx = {
|
|
7608
7631
|
runtimeId: options.id,
|
|
7609
7632
|
taskId: options.taskId,
|
|
@@ -1365,6 +1365,30 @@ function sourceTaskConfigCandidates(projectRoot) {
|
|
|
1365
1365
|
|
|
1366
1366
|
// packages/runtime/src/binary-run.ts
|
|
1367
1367
|
var runtimeBinaryBuildQueue = Promise.resolve();
|
|
1368
|
+
// packages/runtime/src/control-plane/native/pr-review-gate.ts
|
|
1369
|
+
function strictMergeHeadShaFromGate(result, prUrl) {
|
|
1370
|
+
if (!result.approved) {
|
|
1371
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate is not approved.`);
|
|
1372
|
+
}
|
|
1373
|
+
if (result.evidence.prUrl !== prUrl) {
|
|
1374
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate evidence belongs to ${result.evidence.prUrl}.`);
|
|
1375
|
+
}
|
|
1376
|
+
const headSha = result.evidence.headSha?.trim();
|
|
1377
|
+
if (!headSha) {
|
|
1378
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate did not provide a current head SHA.`);
|
|
1379
|
+
}
|
|
1380
|
+
if (!/^[0-9a-f]{40}$/i.test(headSha)) {
|
|
1381
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate head is not a raw 40-character commit SHA.`);
|
|
1382
|
+
}
|
|
1383
|
+
if (!result.evidence.greptile.fresh || result.evidence.greptile.currentHeadSha !== headSha) {
|
|
1384
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate approval is not tied to head ${headSha}.`);
|
|
1385
|
+
}
|
|
1386
|
+
if (result.evidence.greptile.mapping !== "score-5-of-5" && result.evidence.greptile.mapping !== "explicit-approved") {
|
|
1387
|
+
throw new Error(`Refusing to merge ${prUrl}: strict merge gate mapping is ${result.evidence.greptile.mapping}.`);
|
|
1388
|
+
}
|
|
1389
|
+
return headSha;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1368
1392
|
// packages/runtime/src/control-plane/provider/runtime-instructions.ts
|
|
1369
1393
|
var CLAUDE_ROUTER_TOOL_NAMES = [
|
|
1370
1394
|
"`mcp__rig_runtime_tools__read`",
|
|
@@ -1575,12 +1599,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
|
|
|
1575
1599
|
"task-result.json",
|
|
1576
1600
|
"validation-summary.json"
|
|
1577
1601
|
]);
|
|
1578
|
-
function resolveHostRigBinDir(root) {
|
|
1579
|
-
return resolve11(root, ".rig", "bin");
|
|
1580
|
-
}
|
|
1581
1602
|
function isRuntimeGatewayGitPath(candidate) {
|
|
1582
1603
|
return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
|
|
1583
1604
|
}
|
|
1605
|
+
function isRuntimeGatewayGhPath(candidate) {
|
|
1606
|
+
return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
|
|
1607
|
+
}
|
|
1584
1608
|
function resolveOptionalMonorepoRoot(projectRoot) {
|
|
1585
1609
|
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
1586
1610
|
if (runtimeWorkspace && existsSync9(resolve11(runtimeWorkspace, ".git"))) {
|
|
@@ -1615,6 +1639,9 @@ function resolveGitBinary(projectRoot) {
|
|
|
1615
1639
|
}
|
|
1616
1640
|
return "git";
|
|
1617
1641
|
}
|
|
1642
|
+
function escapeRegExp(value) {
|
|
1643
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1644
|
+
}
|
|
1618
1645
|
function safeCurrentTaskId(projectRoot) {
|
|
1619
1646
|
try {
|
|
1620
1647
|
const taskId = currentTaskId(projectRoot);
|
|
@@ -1773,17 +1800,15 @@ function gitOpenPr(options) {
|
|
|
1773
1800
|
const target = options.target || (taskId ? "monorepo" : "project");
|
|
1774
1801
|
let repoRoot = options.projectRoot;
|
|
1775
1802
|
let repoLabel = "project-rig";
|
|
1776
|
-
|
|
1803
|
+
const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
|
|
1777
1804
|
if (target === "monorepo") {
|
|
1778
1805
|
repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
|
|
1779
1806
|
repoLabel = "monorepo";
|
|
1780
|
-
defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
|
|
1781
1807
|
if (taskId) {
|
|
1782
1808
|
gitSyncBranch(options.projectRoot, taskId, "monorepo");
|
|
1783
1809
|
}
|
|
1784
1810
|
} else if (taskId) {
|
|
1785
1811
|
gitSyncBranch(options.projectRoot, taskId, "project");
|
|
1786
|
-
defaultBase = inferProjectBase(options.projectRoot, defaultBase);
|
|
1787
1812
|
}
|
|
1788
1813
|
if (!existsSync9(resolve11(repoRoot, ".git"))) {
|
|
1789
1814
|
throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
|
|
@@ -1792,9 +1817,9 @@ function gitOpenPr(options) {
|
|
|
1792
1817
|
if (!branch || branch === "HEAD") {
|
|
1793
1818
|
throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
|
|
1794
1819
|
}
|
|
1795
|
-
const base = options.base || defaultBase;
|
|
1796
1820
|
const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
|
|
1797
1821
|
const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
|
|
1822
|
+
const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
|
|
1798
1823
|
refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
|
|
1799
1824
|
let reviewer = (options.reviewer || "").trim();
|
|
1800
1825
|
let reviewerSource = reviewer ? "flag" : undefined;
|
|
@@ -1830,10 +1855,11 @@ function gitOpenPr(options) {
|
|
|
1830
1855
|
"",
|
|
1831
1856
|
"## Task",
|
|
1832
1857
|
`- beads: ${taskId || "n/a"}`,
|
|
1858
|
+
...defaultPrRunLines(taskId, repoNameWithOwner),
|
|
1833
1859
|
"",
|
|
1834
1860
|
"## Review",
|
|
1835
1861
|
"- Completion verification will run validation, verifier review, and PR policy checks.",
|
|
1836
|
-
"- When repository policy allows it, Rig
|
|
1862
|
+
"- When repository policy allows it, Rig attempts an immediate strict-gated, head-locked merge after approval."
|
|
1837
1863
|
].join(`
|
|
1838
1864
|
`);
|
|
1839
1865
|
const preCheck = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
|
|
@@ -1921,6 +1947,30 @@ function gitOpenPr(options) {
|
|
|
1921
1947
|
}
|
|
1922
1948
|
return result;
|
|
1923
1949
|
}
|
|
1950
|
+
function defaultPrRunLines(taskId, repoNameWithOwner) {
|
|
1951
|
+
const lines = [];
|
|
1952
|
+
const runId = process.env.RIG_SERVER_RUN_ID?.trim();
|
|
1953
|
+
if (runId) {
|
|
1954
|
+
lines.push(`- Run: ${runId}`);
|
|
1955
|
+
}
|
|
1956
|
+
const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
|
|
1957
|
+
if (closeout) {
|
|
1958
|
+
lines.push(`- ${closeout}`);
|
|
1959
|
+
}
|
|
1960
|
+
return lines;
|
|
1961
|
+
}
|
|
1962
|
+
function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
|
|
1963
|
+
const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
|
|
1964
|
+
if (sourceIssueId) {
|
|
1965
|
+
const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
|
|
1966
|
+
if (match?.[1] && match[2]) {
|
|
1967
|
+
const sourceRepo = match[1];
|
|
1968
|
+
const issueNumber = match[2];
|
|
1969
|
+
return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
|
|
1973
|
+
}
|
|
1924
1974
|
function resolveTaskBranchRef(projectRoot, taskId) {
|
|
1925
1975
|
return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
|
|
1926
1976
|
}
|
|
@@ -2005,61 +2055,45 @@ function gitMergePr(options) {
|
|
|
2005
2055
|
return { status: "already-merged", url: options.pr.url };
|
|
2006
2056
|
}
|
|
2007
2057
|
if (state !== "OPEN") {
|
|
2008
|
-
throw new Error(`Cannot
|
|
2058
|
+
throw new Error(`Cannot merge PR ${options.pr.url}: state is ${state}.`);
|
|
2009
2059
|
}
|
|
2010
2060
|
if (isDraft) {
|
|
2011
|
-
throw new Error(`Cannot
|
|
2061
|
+
throw new Error(`Cannot merge draft PR ${options.pr.url}.`);
|
|
2012
2062
|
}
|
|
2063
|
+
const strictGateHeadSha = strictMergeHeadShaFromGate(options.strictGate, options.pr.url);
|
|
2013
2064
|
const mergeArgs = withGhRepo([gh, "pr", "merge", options.pr.url], repoNameWithOwner);
|
|
2014
2065
|
const method = options.method || "squash";
|
|
2015
2066
|
mergeArgs.push(method === "merge" ? "--merge" : method === "rebase" ? "--rebase" : "--squash");
|
|
2067
|
+
mergeArgs.push("--match-head-commit", strictGateHeadSha);
|
|
2016
2068
|
if (options.deleteBranch !== false) {
|
|
2017
2069
|
mergeArgs.push("--delete-branch");
|
|
2018
2070
|
}
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2033
|
-
adminMergeArgs.push("--admin");
|
|
2034
|
-
const adminMerge = runCapture2(adminMergeArgs, repoRoot);
|
|
2035
|
-
if (adminMerge.exitCode === 0) {
|
|
2036
|
-
const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
|
|
2037
|
-
if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
|
|
2038
|
-
console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
|
|
2039
|
-
return { status: "merged", url: options.pr.url };
|
|
2040
|
-
}
|
|
2041
|
-
throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
|
|
2042
|
-
}
|
|
2043
|
-
const adminMergeMessage = `${adminMerge.stderr}
|
|
2044
|
-
${adminMerge.stdout}`.trim();
|
|
2045
|
-
if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
|
|
2046
|
-
throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
|
|
2047
|
-
}
|
|
2071
|
+
const directMerge = runCapture2(mergeArgs, repoRoot);
|
|
2072
|
+
if (directMerge.exitCode === 0) {
|
|
2073
|
+
console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
|
|
2074
|
+
return { status: "merged", url: options.pr.url };
|
|
2075
|
+
}
|
|
2076
|
+
const postDirectState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
|
|
2077
|
+
if (canAdminMergeApprovedPr(postDirectState)) {
|
|
2078
|
+
const adminMergeArgs = [...mergeArgs, "--admin"];
|
|
2079
|
+
const adminMerge = runCapture2(adminMergeArgs, repoRoot);
|
|
2080
|
+
if (adminMerge.exitCode === 0) {
|
|
2081
|
+
const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
|
|
2082
|
+
if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
|
|
2083
|
+
console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
|
|
2084
|
+
return { status: "merged", url: options.pr.url };
|
|
2048
2085
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2086
|
+
throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
|
|
2087
|
+
}
|
|
2088
|
+
const adminMergeMessage = `${adminMerge.stderr}
|
|
2089
|
+
${adminMerge.stdout}`.trim();
|
|
2090
|
+
if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
|
|
2091
|
+
throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
|
|
2051
2092
|
}
|
|
2052
|
-
throw new Error(`Auto-merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub did not report a merged or auto-merge-enabled state.`);
|
|
2053
|
-
}
|
|
2054
|
-
const autoMergeMessage = `${autoMerge.stderr}
|
|
2055
|
-
${autoMerge.stdout}`.trim();
|
|
2056
|
-
const autoMergeUnsupported = /auto.?merge.*(not enabled|not allowed|disabled|unsupported)|enablePullRequestAutoMerge|Auto merge is not allowed/i.test(autoMergeMessage);
|
|
2057
|
-
if (!autoMergeUnsupported) {
|
|
2058
|
-
throw new Error(`Failed to auto-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${autoMergeMessage}`);
|
|
2059
2093
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2094
|
+
const directMergeMessage = `${directMerge.stderr}
|
|
2095
|
+
${directMerge.stdout}`.trim();
|
|
2096
|
+
throw new Error(`Failed to merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${directMergeMessage}`);
|
|
2063
2097
|
}
|
|
2064
2098
|
function assertPrHasNoGitConflicts(prState, repoLabel, baseRef) {
|
|
2065
2099
|
const mergeable = prState.mergeable.toUpperCase();
|
|
@@ -2171,32 +2205,19 @@ function resolveGithubCliBinary(projectRoot) {
|
|
|
2171
2205
|
if (explicit) {
|
|
2172
2206
|
candidates.add(explicit);
|
|
2173
2207
|
}
|
|
2208
|
+
for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
|
|
2209
|
+
candidates.add(candidate);
|
|
2210
|
+
}
|
|
2174
2211
|
const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
|
|
2175
2212
|
for (const entry of explicitPathEntries) {
|
|
2176
2213
|
candidates.add(resolve11(entry, "gh"));
|
|
2177
2214
|
}
|
|
2178
|
-
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
|
|
2179
|
-
if (hostProjectRoot) {
|
|
2180
|
-
candidates.add(resolve11(resolveHostRigBinDir(hostProjectRoot), "gh"));
|
|
2181
|
-
}
|
|
2182
|
-
candidates.add(resolve11(resolveHostRigBinDir(projectRoot), "gh"));
|
|
2183
|
-
const runtimeContext = loadRuntimeContextFromEnv();
|
|
2184
|
-
if (runtimeContext?.binDir) {
|
|
2185
|
-
candidates.add(resolve11(runtimeContext.binDir, "gh"));
|
|
2186
|
-
}
|
|
2187
|
-
const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
|
|
2188
|
-
if (runtimeHome) {
|
|
2189
|
-
candidates.add(resolve11(runtimeHome, "bin", "gh"));
|
|
2190
|
-
}
|
|
2191
|
-
for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
|
|
2192
|
-
candidates.add(candidate);
|
|
2193
|
-
}
|
|
2194
2215
|
const bunResolved = Bun.which("gh");
|
|
2195
2216
|
if (bunResolved) {
|
|
2196
2217
|
candidates.add(bunResolved);
|
|
2197
2218
|
}
|
|
2198
2219
|
for (const candidate of candidates) {
|
|
2199
|
-
if (candidate && existsSync9(candidate)) {
|
|
2220
|
+
if (candidate && existsSync9(candidate) && !isRuntimeGatewayGhPath(candidate)) {
|
|
2200
2221
|
return candidate;
|
|
2201
2222
|
}
|
|
2202
2223
|
}
|
|
@@ -2345,6 +2366,32 @@ function withGhRepo(command, repoNameWithOwner) {
|
|
|
2345
2366
|
}
|
|
2346
2367
|
return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
|
|
2347
2368
|
}
|
|
2369
|
+
function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
|
|
2370
|
+
const remote = remoteName || "origin";
|
|
2371
|
+
const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
|
|
2372
|
+
if (symbolic.exitCode === 0) {
|
|
2373
|
+
const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
|
|
2374
|
+
if (ref && ref !== "HEAD") {
|
|
2375
|
+
return ref;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
|
|
2379
|
+
if (lsRemote.exitCode === 0) {
|
|
2380
|
+
const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
|
|
2381
|
+
if (match?.[1]) {
|
|
2382
|
+
return match[1];
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
const gh = resolveGithubCliBinary(projectRoot);
|
|
2386
|
+
if (gh && repoNameWithOwner) {
|
|
2387
|
+
const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
|
|
2388
|
+
const branch = api.exitCode === 0 ? api.stdout.trim() : "";
|
|
2389
|
+
if (branch) {
|
|
2390
|
+
return branch;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
return fallback;
|
|
2394
|
+
}
|
|
2348
2395
|
function inferProjectBase(projectRoot, fallback) {
|
|
2349
2396
|
const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
|
|
2350
2397
|
if (containing.exitCode !== 0) {
|
|
@@ -2726,6 +2773,10 @@ function runtimeGitEnv(projectRoot) {
|
|
|
2726
2773
|
}
|
|
2727
2774
|
env[key] = value;
|
|
2728
2775
|
}
|
|
2776
|
+
const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
2777
|
+
if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
|
|
2778
|
+
env.GITHUB_TOKEN = rigGithubToken;
|
|
2779
|
+
}
|
|
2729
2780
|
if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
|
|
2730
2781
|
env.GITHUB_TOKEN = env.GH_TOKEN;
|
|
2731
2782
|
}
|
|
@@ -2749,6 +2800,13 @@ function runtimeGitEnv(projectRoot) {
|
|
|
2749
2800
|
if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
|
|
2750
2801
|
env.GH_TOKEN = env.GITHUB_TOKEN;
|
|
2751
2802
|
}
|
|
2803
|
+
const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
|
|
2804
|
+
if (gitHubToken) {
|
|
2805
|
+
env.RIG_GITHUB_TOKEN = gitHubToken;
|
|
2806
|
+
env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
|
|
2807
|
+
env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
|
|
2808
|
+
applyGitHubCredentialHelperEnv(env);
|
|
2809
|
+
}
|
|
2752
2810
|
if (runtimeKnownHosts && existsSync9(runtimeKnownHosts)) {
|
|
2753
2811
|
const sshParts = [
|
|
2754
2812
|
"ssh",
|
|
@@ -2765,6 +2823,14 @@ function runtimeGitEnv(projectRoot) {
|
|
|
2765
2823
|
}
|
|
2766
2824
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
2767
2825
|
}
|
|
2826
|
+
function applyGitHubCredentialHelperEnv(env) {
|
|
2827
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
2828
|
+
env.GIT_CONFIG_COUNT = "2";
|
|
2829
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
2830
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
2831
|
+
env.GIT_CONFIG_KEY_1 = "credential.helper";
|
|
2832
|
+
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';
|
|
2833
|
+
}
|
|
2768
2834
|
function loadPersistedRuntimeSecrets(runtimeRoot) {
|
|
2769
2835
|
if (!runtimeRoot) {
|
|
2770
2836
|
return {};
|