@h-rig/runtime 0.0.6-alpha.3 → 0.0.6-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/bin/rig-agent-dispatch.js +1165 -785
  2. package/dist/bin/rig-agent.js +458 -389
  3. package/dist/src/control-plane/agent-wrapper.js +1191 -504
  4. package/dist/src/control-plane/authority-files.js +12 -6
  5. package/dist/src/control-plane/harness-main.js +2186 -1786
  6. package/dist/src/control-plane/hooks/completion-verification.js +2084 -1019
  7. package/dist/src/control-plane/hooks/inject-context.js +193 -139
  8. package/dist/src/control-plane/hooks/submodule-branch.js +603 -545
  9. package/dist/src/control-plane/hooks/task-runtime-start.js +603 -545
  10. package/dist/src/control-plane/materialize-task-config.js +64 -8
  11. package/dist/src/control-plane/native/git-ops.js +90 -64
  12. package/dist/src/control-plane/native/harness-cli.js +1989 -682
  13. package/dist/src/control-plane/native/pr-automation.js +1657 -54
  14. package/dist/src/control-plane/native/pr-review-gate.js +1455 -0
  15. package/dist/src/control-plane/native/repo-ops.js +3 -0
  16. package/dist/src/control-plane/native/run-ops.js +39 -13
  17. package/dist/src/control-plane/native/task-ops.js +1819 -527
  18. package/dist/src/control-plane/native/validator.js +163 -109
  19. package/dist/src/control-plane/native/verifier.js +1616 -323
  20. package/dist/src/control-plane/native/workspace-ops.js +12 -6
  21. package/dist/src/control-plane/pi-sessiond/bin.js +793 -0
  22. package/dist/src/control-plane/pi-sessiond/client.js +41 -0
  23. package/dist/src/control-plane/pi-sessiond/event-hub.js +59 -0
  24. package/dist/src/control-plane/pi-sessiond/extension-ui-context.js +198 -0
  25. package/dist/src/control-plane/pi-sessiond/launcher.js +173 -0
  26. package/dist/src/control-plane/pi-sessiond/server.js +802 -0
  27. package/dist/src/control-plane/pi-sessiond/session-service.js +540 -0
  28. package/dist/src/control-plane/pi-sessiond/types.js +1 -0
  29. package/dist/src/control-plane/plugin-host-context.js +54 -0
  30. package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +3 -0
  31. package/dist/src/control-plane/runtime/image/index.js +3 -0
  32. package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +3 -0
  33. package/dist/src/control-plane/runtime/image.js +3 -0
  34. package/dist/src/control-plane/runtime/index.js +517 -722
  35. package/dist/src/control-plane/runtime/isolation/home.js +28 -6
  36. package/dist/src/control-plane/runtime/isolation/index.js +541 -461
  37. package/dist/src/control-plane/runtime/isolation/runner.js +28 -6
  38. package/dist/src/control-plane/runtime/isolation/shared.js +9 -6
  39. package/dist/src/control-plane/runtime/isolation.js +541 -461
  40. package/dist/src/control-plane/runtime/plugin-mode.js +3 -27
  41. package/dist/src/control-plane/runtime/queue.js +458 -385
  42. package/dist/src/control-plane/runtime/snapshot/task-run.js +3 -0
  43. package/dist/src/control-plane/runtime/task-run-snapshot.js +3 -0
  44. package/dist/src/control-plane/skill-materializer.js +46 -0
  45. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +14 -2
  46. package/dist/src/control-plane/tasks/source-lifecycle.js +86 -32
  47. package/dist/src/index.js +27 -298
  48. package/dist/src/layout.js +12 -7
  49. package/dist/src/local-server.js +20 -14
  50. package/native/darwin-arm64/rig-git +0 -0
  51. package/native/darwin-arm64/rig-git.build-manifest.json +1 -1
  52. package/native/darwin-arm64/rig-shell +0 -0
  53. package/native/darwin-arm64/rig-shell.build-manifest.json +1 -1
  54. package/native/darwin-arm64/rig-tools +0 -0
  55. package/native/darwin-arm64/rig-tools.build-manifest.json +1 -1
  56. package/native/darwin-arm64/runtime-native.dylib +0 -0
  57. package/package.json +8 -6
  58. package/dist/src/control-plane/runtime/plugins.js +0 -1131
  59. package/dist/src/plugins.js +0 -329
@@ -300,6 +300,50 @@ function safeReadJson(path) {
300
300
  var MARKER_PLUGIN = "_rigPlugin", MARKER_HOOK_ID = "_rigHookId";
301
301
  var init_hook_materializer = () => {};
302
302
 
303
+ // packages/runtime/src/control-plane/skill-materializer.ts
304
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
305
+ import { resolve as resolve2 } from "path";
306
+ import { loadSkill } from "@rig/skill-loader";
307
+ function skillDirName(id) {
308
+ return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
309
+ }
310
+ async function materializeSkills(projectRoot, entries) {
311
+ const skillsRoot = resolve2(projectRoot, ".pi", "skills");
312
+ if (existsSync3(skillsRoot)) {
313
+ for (const name of readdirSync(skillsRoot)) {
314
+ const dir = resolve2(skillsRoot, name);
315
+ if (existsSync3(resolve2(dir, MARKER_FILENAME))) {
316
+ rmSync(dir, { recursive: true, force: true });
317
+ }
318
+ }
319
+ }
320
+ const written = [];
321
+ for (const { pluginName, skill } of entries) {
322
+ const sourcePath = resolve2(projectRoot, skill.path);
323
+ if (!existsSync3(sourcePath)) {
324
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
325
+ continue;
326
+ }
327
+ let body;
328
+ try {
329
+ await loadSkill(sourcePath);
330
+ body = readFileSync2(sourcePath, "utf-8");
331
+ } catch (err) {
332
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
333
+ continue;
334
+ }
335
+ const dir = resolve2(skillsRoot, skillDirName(skill.id));
336
+ mkdirSync2(dir, { recursive: true });
337
+ writeFileSync2(resolve2(dir, "SKILL.md"), body, "utf-8");
338
+ writeFileSync2(resolve2(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
339
+ `, "utf-8");
340
+ written.push({ id: skill.id, pluginName, directory: dir });
341
+ }
342
+ return written;
343
+ }
344
+ var MARKER_FILENAME = ".rig-plugin";
345
+ var init_skill_materializer = () => {};
346
+
303
347
  // packages/runtime/src/control-plane/plugin-host-context.ts
304
348
  var exports_plugin_host_context = {};
305
349
  __export(exports_plugin_host_context, {
@@ -342,6 +386,17 @@ async function buildPluginHostContext(projectRoot) {
342
386
  } catch (err) {
343
387
  console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
344
388
  }
389
+ try {
390
+ const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
391
+ pluginName: plugin.name,
392
+ skill
393
+ })));
394
+ if (skillEntries.length > 0) {
395
+ await materializeSkills(projectRoot, skillEntries);
396
+ }
397
+ } catch (err) {
398
+ console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
399
+ }
345
400
  return {
346
401
  config,
347
402
  pluginHost,
@@ -357,13 +412,14 @@ var init_plugin_host_context = __esm(() => {
357
412
  init_registry();
358
413
  init_runtime_registration();
359
414
  init_hook_materializer();
415
+ init_skill_materializer();
360
416
  });
361
417
 
362
418
  // packages/runtime/src/control-plane/materialize-task-config.ts
363
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
364
- import { dirname as dirname2, resolve as resolve2 } from "path";
419
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
420
+ import { dirname as dirname2, resolve as resolve3 } from "path";
365
421
  async function materializePluginHostTaskConfig(projectRoot) {
366
- const hasConfig = existsSync3(resolve2(projectRoot, "rig.config.ts")) || existsSync3(resolve2(projectRoot, "rig.config.json"));
422
+ const hasConfig = existsSync4(resolve3(projectRoot, "rig.config.ts")) || existsSync4(resolve3(projectRoot, "rig.config.json"));
367
423
  if (!hasConfig)
368
424
  return null;
369
425
  const { buildPluginHostContext: buildPluginHostContext2 } = await Promise.resolve().then(() => (init_plugin_host_context(), exports_plugin_host_context));
@@ -383,8 +439,8 @@ async function materializePluginHostTaskConfig(projectRoot) {
383
439
  const tasks = await source.list();
384
440
  if (tasks.length === 0)
385
441
  return null;
386
- const configPath = resolve2(projectRoot, ".rig", "task-config.json");
387
- const existing = existsSync3(configPath) ? safeJsonRead(configPath) : {};
442
+ const configPath = resolve3(projectRoot, ".rig", "task-config.json");
443
+ const existing = existsSync4(configPath) ? safeJsonRead(configPath) : {};
388
444
  const merged = { ...existing };
389
445
  let syncedCount = 0;
390
446
  for (const task of tasks) {
@@ -400,8 +456,8 @@ async function materializePluginHostTaskConfig(projectRoot) {
400
456
  merged[id] = synthesizeEntry(t, ctx.config.taskSource);
401
457
  syncedCount += 1;
402
458
  }
403
- mkdirSync2(dirname2(configPath), { recursive: true });
404
- writeFileSync2(configPath, `${JSON.stringify(merged, null, 2)}
459
+ mkdirSync3(dirname2(configPath), { recursive: true });
460
+ writeFileSync3(configPath, `${JSON.stringify(merged, null, 2)}
405
461
  `, "utf-8");
406
462
  return { configPath, syncedCount };
407
463
  }
@@ -442,7 +498,7 @@ function materializedTaskSource(taskSource) {
442
498
  }
443
499
  function safeJsonRead(path) {
444
500
  try {
445
- const parsed = JSON.parse(readFileSync2(path, "utf-8"));
501
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
446
502
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
447
503
  } catch {
448
504
  return {};
@@ -311,6 +311,9 @@ function getScopeRules() {
311
311
  return activeRules;
312
312
  }
313
313
 
314
+ // packages/runtime/src/control-plane/skill-materializer.ts
315
+ import { loadSkill } from "@rig/skill-loader";
316
+
314
317
  // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
315
318
  var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
316
319
 
@@ -1365,6 +1368,30 @@ function sourceTaskConfigCandidates(projectRoot) {
1365
1368
 
1366
1369
  // packages/runtime/src/binary-run.ts
1367
1370
  var runtimeBinaryBuildQueue = Promise.resolve();
1371
+ // packages/runtime/src/control-plane/native/pr-review-gate.ts
1372
+ function strictMergeHeadShaFromGate(result, prUrl) {
1373
+ if (!result.approved) {
1374
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate is not approved.`);
1375
+ }
1376
+ if (result.evidence.prUrl !== prUrl) {
1377
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate evidence belongs to ${result.evidence.prUrl}.`);
1378
+ }
1379
+ const headSha = result.evidence.headSha?.trim();
1380
+ if (!headSha) {
1381
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate did not provide a current head SHA.`);
1382
+ }
1383
+ if (!/^[0-9a-f]{40}$/i.test(headSha)) {
1384
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate head is not a raw 40-character commit SHA.`);
1385
+ }
1386
+ if (!result.evidence.greptile.fresh || result.evidence.greptile.currentHeadSha !== headSha) {
1387
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate approval is not tied to head ${headSha}.`);
1388
+ }
1389
+ if (result.evidence.greptile.mapping !== "score-5-of-5" && result.evidence.greptile.mapping !== "explicit-approved") {
1390
+ throw new Error(`Refusing to merge ${prUrl}: strict merge gate mapping is ${result.evidence.greptile.mapping}.`);
1391
+ }
1392
+ return headSha;
1393
+ }
1394
+
1368
1395
  // packages/runtime/src/control-plane/provider/runtime-instructions.ts
1369
1396
  var CLAUDE_ROUTER_TOOL_NAMES = [
1370
1397
  "`mcp__rig_runtime_tools__read`",
@@ -1575,12 +1602,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
1575
1602
  "task-result.json",
1576
1603
  "validation-summary.json"
1577
1604
  ]);
1578
- function resolveHostRigBinDir(root) {
1579
- return resolve11(root, ".rig", "bin");
1580
- }
1581
1605
  function isRuntimeGatewayGitPath(candidate) {
1582
1606
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
1583
1607
  }
1608
+ function isRuntimeGatewayGhPath(candidate) {
1609
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
1610
+ }
1584
1611
  function resolveOptionalMonorepoRoot(projectRoot) {
1585
1612
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
1586
1613
  if (runtimeWorkspace && existsSync9(resolve11(runtimeWorkspace, ".git"))) {
@@ -1615,6 +1642,9 @@ function resolveGitBinary(projectRoot) {
1615
1642
  }
1616
1643
  return "git";
1617
1644
  }
1645
+ function escapeRegExp(value) {
1646
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1647
+ }
1618
1648
  function safeCurrentTaskId(projectRoot) {
1619
1649
  try {
1620
1650
  const taskId = currentTaskId(projectRoot);
@@ -1828,10 +1858,11 @@ function gitOpenPr(options) {
1828
1858
  "",
1829
1859
  "## Task",
1830
1860
  `- beads: ${taskId || "n/a"}`,
1861
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
1831
1862
  "",
1832
1863
  "## Review",
1833
1864
  "- Completion verification will run validation, verifier review, and PR policy checks.",
1834
- "- When repository policy allows it, Rig enables GitHub auto-merge after approval."
1865
+ "- When repository policy allows it, Rig attempts an immediate strict-gated, head-locked merge after approval."
1835
1866
  ].join(`
1836
1867
  `);
1837
1868
  const preCheck = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
@@ -1919,6 +1950,30 @@ function gitOpenPr(options) {
1919
1950
  }
1920
1951
  return result;
1921
1952
  }
1953
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
1954
+ const lines = [];
1955
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
1956
+ if (runId) {
1957
+ lines.push(`- Run: ${runId}`);
1958
+ }
1959
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
1960
+ if (closeout) {
1961
+ lines.push(`- ${closeout}`);
1962
+ }
1963
+ return lines;
1964
+ }
1965
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
1966
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
1967
+ if (sourceIssueId) {
1968
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
1969
+ if (match?.[1] && match[2]) {
1970
+ const sourceRepo = match[1];
1971
+ const issueNumber = match[2];
1972
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
1973
+ }
1974
+ }
1975
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
1976
+ }
1922
1977
  function resolveTaskBranchRef(projectRoot, taskId) {
1923
1978
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1924
1979
  }
@@ -2003,61 +2058,45 @@ function gitMergePr(options) {
2003
2058
  return { status: "already-merged", url: options.pr.url };
2004
2059
  }
2005
2060
  if (state !== "OPEN") {
2006
- throw new Error(`Cannot auto-merge PR ${options.pr.url}: state is ${state}.`);
2061
+ throw new Error(`Cannot merge PR ${options.pr.url}: state is ${state}.`);
2007
2062
  }
2008
2063
  if (isDraft) {
2009
- throw new Error(`Cannot auto-merge draft PR ${options.pr.url}.`);
2064
+ throw new Error(`Cannot merge draft PR ${options.pr.url}.`);
2010
2065
  }
2066
+ const strictGateHeadSha = strictMergeHeadShaFromGate(options.strictGate, options.pr.url);
2011
2067
  const mergeArgs = withGhRepo([gh, "pr", "merge", options.pr.url], repoNameWithOwner);
2012
2068
  const method = options.method || "squash";
2013
2069
  mergeArgs.push(method === "merge" ? "--merge" : method === "rebase" ? "--rebase" : "--squash");
2070
+ mergeArgs.push("--match-head-commit", strictGateHeadSha);
2014
2071
  if (options.deleteBranch !== false) {
2015
2072
  mergeArgs.push("--delete-branch");
2016
2073
  }
2017
- const autoMergeArgs = [...mergeArgs, "--auto"];
2018
- const autoMerge = runCapture2(autoMergeArgs, repoRoot);
2019
- if (autoMerge.exitCode === 0) {
2020
- const postAutoMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2021
- if (postAutoMergeState.state === "MERGED" || postAutoMergeState.mergedAt) {
2022
- console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
2023
- return { status: "merged", url: options.pr.url };
2024
- }
2025
- if (postAutoMergeState.state === "OPEN" && postAutoMergeState.autoMergeRequest) {
2026
- if (canAdminMergeApprovedPr(postAutoMergeState)) {
2027
- const adminMergeArgs = [...mergeArgs];
2028
- if (postAutoMergeState.headRefOid) {
2029
- adminMergeArgs.push("--match-head-commit", postAutoMergeState.headRefOid);
2030
- }
2031
- adminMergeArgs.push("--admin");
2032
- const adminMerge = runCapture2(adminMergeArgs, repoRoot);
2033
- if (adminMerge.exitCode === 0) {
2034
- const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2035
- if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
2036
- console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
2037
- return { status: "merged", url: options.pr.url };
2038
- }
2039
- throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
2040
- }
2041
- const adminMergeMessage = `${adminMerge.stderr}
2042
- ${adminMerge.stdout}`.trim();
2043
- if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
2044
- throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
2045
- }
2074
+ const directMerge = runCapture2(mergeArgs, repoRoot);
2075
+ if (directMerge.exitCode === 0) {
2076
+ console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
2077
+ return { status: "merged", url: options.pr.url };
2078
+ }
2079
+ const postDirectState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2080
+ if (canAdminMergeApprovedPr(postDirectState)) {
2081
+ const adminMergeArgs = [...mergeArgs, "--admin"];
2082
+ const adminMerge = runCapture2(adminMergeArgs, repoRoot);
2083
+ if (adminMerge.exitCode === 0) {
2084
+ const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2085
+ if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
2086
+ console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
2087
+ return { status: "merged", url: options.pr.url };
2046
2088
  }
2047
- console.log(`Auto-merge enabled (${options.pr.repoLabel}): ${options.pr.url}`);
2048
- return { status: "auto-merge-enabled", url: options.pr.url };
2089
+ throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
2090
+ }
2091
+ const adminMergeMessage = `${adminMerge.stderr}
2092
+ ${adminMerge.stdout}`.trim();
2093
+ if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
2094
+ throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
2049
2095
  }
2050
- 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.`);
2051
- }
2052
- const autoMergeMessage = `${autoMerge.stderr}
2053
- ${autoMerge.stdout}`.trim();
2054
- const autoMergeUnsupported = /auto.?merge.*(not enabled|not allowed|disabled|unsupported)|enablePullRequestAutoMerge|Auto merge is not allowed/i.test(autoMergeMessage);
2055
- if (!autoMergeUnsupported) {
2056
- throw new Error(`Failed to auto-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${autoMergeMessage}`);
2057
2096
  }
2058
- runOrThrow(options.projectRoot, mergeArgs, `Failed to merge PR ${options.pr.url} in ${options.pr.repoLabel}`);
2059
- console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
2060
- return { status: "merged", url: options.pr.url };
2097
+ const directMergeMessage = `${directMerge.stderr}
2098
+ ${directMerge.stdout}`.trim();
2099
+ throw new Error(`Failed to merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${directMergeMessage}`);
2061
2100
  }
2062
2101
  function assertPrHasNoGitConflicts(prState, repoLabel, baseRef) {
2063
2102
  const mergeable = prState.mergeable.toUpperCase();
@@ -2169,32 +2208,19 @@ function resolveGithubCliBinary(projectRoot) {
2169
2208
  if (explicit) {
2170
2209
  candidates.add(explicit);
2171
2210
  }
2211
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
2212
+ candidates.add(candidate);
2213
+ }
2172
2214
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
2173
2215
  for (const entry of explicitPathEntries) {
2174
2216
  candidates.add(resolve11(entry, "gh"));
2175
2217
  }
2176
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
2177
- if (hostProjectRoot) {
2178
- candidates.add(resolve11(resolveHostRigBinDir(hostProjectRoot), "gh"));
2179
- }
2180
- candidates.add(resolve11(resolveHostRigBinDir(projectRoot), "gh"));
2181
- const runtimeContext = loadRuntimeContextFromEnv();
2182
- if (runtimeContext?.binDir) {
2183
- candidates.add(resolve11(runtimeContext.binDir, "gh"));
2184
- }
2185
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
2186
- if (runtimeHome) {
2187
- candidates.add(resolve11(runtimeHome, "bin", "gh"));
2188
- }
2189
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
2190
- candidates.add(candidate);
2191
- }
2192
2218
  const bunResolved = Bun.which("gh");
2193
2219
  if (bunResolved) {
2194
2220
  candidates.add(bunResolved);
2195
2221
  }
2196
2222
  for (const candidate of candidates) {
2197
- if (candidate && existsSync9(candidate)) {
2223
+ if (candidate && existsSync9(candidate) && !isRuntimeGatewayGhPath(candidate)) {
2198
2224
  return candidate;
2199
2225
  }
2200
2226
  }