@h-rig/runtime 0.0.6-alpha.2 → 0.0.6-alpha.4

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 (40) hide show
  1. package/dist/bin/rig-agent-dispatch.js +93 -14
  2. package/dist/bin/rig-agent.js +83 -26
  3. package/dist/src/control-plane/agent-wrapper.js +93 -14
  4. package/dist/src/control-plane/harness-main.js +83 -26
  5. package/dist/src/control-plane/hooks/completion-verification.js +85 -28
  6. package/dist/src/control-plane/hooks/inject-context.js +2 -2
  7. package/dist/src/control-plane/hooks/submodule-branch.js +26 -3
  8. package/dist/src/control-plane/hooks/task-runtime-start.js +26 -3
  9. package/dist/src/control-plane/native/git-ops.js +81 -24
  10. package/dist/src/control-plane/native/harness-cli.js +83 -26
  11. package/dist/src/control-plane/native/pr-automation.js +87 -16
  12. package/dist/src/control-plane/native/run-ops.js +23 -6
  13. package/dist/src/control-plane/native/task-ops.js +2 -2
  14. package/dist/src/control-plane/native/validator.js +2 -2
  15. package/dist/src/control-plane/native/verifier.js +2 -2
  16. package/dist/src/control-plane/runtime/index.js +38 -9
  17. package/dist/src/control-plane/runtime/isolation/home.js +31 -6
  18. package/dist/src/control-plane/runtime/isolation/index.js +38 -9
  19. package/dist/src/control-plane/runtime/isolation/runner.js +31 -6
  20. package/dist/src/control-plane/runtime/isolation/shared.js +9 -6
  21. package/dist/src/control-plane/runtime/isolation.js +38 -9
  22. package/dist/src/control-plane/runtime/queue.js +38 -9
  23. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +14 -2
  24. package/dist/src/control-plane/tasks/source-lifecycle.js +2 -2
  25. package/native/darwin-arm64/{bin/rig-git → rig-git} +0 -0
  26. package/native/darwin-arm64/rig-git.build-manifest.json +4 -0
  27. package/native/darwin-arm64/{bin/rig-shell → rig-shell} +0 -0
  28. package/native/darwin-arm64/rig-shell.build-manifest.json +4 -0
  29. package/native/darwin-arm64/{bin/rig-tools → rig-tools} +0 -0
  30. package/native/darwin-arm64/rig-tools.build-manifest.json +4 -0
  31. package/native/darwin-arm64/{lib/runtime-native.dylib → runtime-native.dylib} +0 -0
  32. package/package.json +6 -6
  33. package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
  34. package/native/darwin-arm64/manifest.json +0 -1
  35. package/native/linux-x64/bin/rig-git +0 -0
  36. package/native/linux-x64/bin/rig-shell +0 -0
  37. package/native/linux-x64/bin/rig-tools +0 -0
  38. package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
  39. package/native/linux-x64/lib/runtime-native.so +0 -0
  40. package/native/linux-x64/manifest.json +0 -1
@@ -1575,12 +1575,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
1575
1575
  "task-result.json",
1576
1576
  "validation-summary.json"
1577
1577
  ]);
1578
- function resolveHostRigBinDir(root) {
1579
- return resolve11(root, ".rig", "bin");
1580
- }
1581
1578
  function isRuntimeGatewayGitPath(candidate) {
1582
1579
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
1583
1580
  }
1581
+ function isRuntimeGatewayGhPath(candidate) {
1582
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
1583
+ }
1584
1584
  function resolveOptionalMonorepoRoot(projectRoot) {
1585
1585
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
1586
1586
  if (runtimeWorkspace && existsSync9(resolve11(runtimeWorkspace, ".git"))) {
@@ -1615,6 +1615,9 @@ function resolveGitBinary(projectRoot) {
1615
1615
  }
1616
1616
  return "git";
1617
1617
  }
1618
+ function escapeRegExp(value) {
1619
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1620
+ }
1618
1621
  function safeCurrentTaskId(projectRoot) {
1619
1622
  try {
1620
1623
  const taskId = currentTaskId(projectRoot);
@@ -1773,17 +1776,15 @@ function gitOpenPr(options) {
1773
1776
  const target = options.target || (taskId ? "monorepo" : "project");
1774
1777
  let repoRoot = options.projectRoot;
1775
1778
  let repoLabel = "project-rig";
1776
- let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
1779
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
1777
1780
  if (target === "monorepo") {
1778
1781
  repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
1779
1782
  repoLabel = "monorepo";
1780
- defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
1781
1783
  if (taskId) {
1782
1784
  gitSyncBranch(options.projectRoot, taskId, "monorepo");
1783
1785
  }
1784
1786
  } else if (taskId) {
1785
1787
  gitSyncBranch(options.projectRoot, taskId, "project");
1786
- defaultBase = inferProjectBase(options.projectRoot, defaultBase);
1787
1788
  }
1788
1789
  if (!existsSync9(resolve11(repoRoot, ".git"))) {
1789
1790
  throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
@@ -1792,9 +1793,9 @@ function gitOpenPr(options) {
1792
1793
  if (!branch || branch === "HEAD") {
1793
1794
  throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
1794
1795
  }
1795
- const base = options.base || defaultBase;
1796
1796
  const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
1797
1797
  const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
1798
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
1798
1799
  refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
1799
1800
  let reviewer = (options.reviewer || "").trim();
1800
1801
  let reviewerSource = reviewer ? "flag" : undefined;
@@ -1830,6 +1831,7 @@ function gitOpenPr(options) {
1830
1831
  "",
1831
1832
  "## Task",
1832
1833
  `- beads: ${taskId || "n/a"}`,
1834
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
1833
1835
  "",
1834
1836
  "## Review",
1835
1837
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -1921,6 +1923,29 @@ function gitOpenPr(options) {
1921
1923
  }
1922
1924
  return result;
1923
1925
  }
1926
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
1927
+ const lines = [];
1928
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
1929
+ if (runId) {
1930
+ lines.push(`- Run: ${runId}`);
1931
+ }
1932
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
1933
+ if (closeout) {
1934
+ lines.push(`- ${closeout}`);
1935
+ }
1936
+ return lines;
1937
+ }
1938
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
1939
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
1940
+ if (sourceIssueId) {
1941
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
1942
+ if (match) {
1943
+ const [, sourceRepo, issueNumber] = match;
1944
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
1945
+ }
1946
+ }
1947
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
1948
+ }
1924
1949
  function resolveTaskBranchRef(projectRoot, taskId) {
1925
1950
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1926
1951
  }
@@ -2171,32 +2196,19 @@ function resolveGithubCliBinary(projectRoot) {
2171
2196
  if (explicit) {
2172
2197
  candidates.add(explicit);
2173
2198
  }
2199
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
2200
+ candidates.add(candidate);
2201
+ }
2174
2202
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
2175
2203
  for (const entry of explicitPathEntries) {
2176
2204
  candidates.add(resolve11(entry, "gh"));
2177
2205
  }
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
2206
  const bunResolved = Bun.which("gh");
2195
2207
  if (bunResolved) {
2196
2208
  candidates.add(bunResolved);
2197
2209
  }
2198
2210
  for (const candidate of candidates) {
2199
- if (candidate && existsSync9(candidate)) {
2211
+ if (candidate && existsSync9(candidate) && !isRuntimeGatewayGhPath(candidate)) {
2200
2212
  return candidate;
2201
2213
  }
2202
2214
  }
@@ -2345,6 +2357,32 @@ function withGhRepo(command, repoNameWithOwner) {
2345
2357
  }
2346
2358
  return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
2347
2359
  }
2360
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
2361
+ const remote = remoteName || "origin";
2362
+ const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
2363
+ if (symbolic.exitCode === 0) {
2364
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
2365
+ if (ref && ref !== "HEAD") {
2366
+ return ref;
2367
+ }
2368
+ }
2369
+ const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
2370
+ if (lsRemote.exitCode === 0) {
2371
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
2372
+ if (match?.[1]) {
2373
+ return match[1];
2374
+ }
2375
+ }
2376
+ const gh = resolveGithubCliBinary(projectRoot);
2377
+ if (gh && repoNameWithOwner) {
2378
+ const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
2379
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
2380
+ if (branch) {
2381
+ return branch;
2382
+ }
2383
+ }
2384
+ return fallback;
2385
+ }
2348
2386
  function inferProjectBase(projectRoot, fallback) {
2349
2387
  const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
2350
2388
  if (containing.exitCode !== 0) {
@@ -2726,6 +2764,10 @@ function runtimeGitEnv(projectRoot) {
2726
2764
  }
2727
2765
  env[key] = value;
2728
2766
  }
2767
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
2768
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
2769
+ env.GITHUB_TOKEN = rigGithubToken;
2770
+ }
2729
2771
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
2730
2772
  env.GITHUB_TOKEN = env.GH_TOKEN;
2731
2773
  }
@@ -2749,6 +2791,13 @@ function runtimeGitEnv(projectRoot) {
2749
2791
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
2750
2792
  env.GH_TOKEN = env.GITHUB_TOKEN;
2751
2793
  }
2794
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
2795
+ if (gitHubToken) {
2796
+ env.RIG_GITHUB_TOKEN = gitHubToken;
2797
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
2798
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
2799
+ applyGitHubCredentialHelperEnv(env);
2800
+ }
2752
2801
  if (runtimeKnownHosts && existsSync9(runtimeKnownHosts)) {
2753
2802
  const sshParts = [
2754
2803
  "ssh",
@@ -2765,6 +2814,14 @@ function runtimeGitEnv(projectRoot) {
2765
2814
  }
2766
2815
  return Object.keys(env).length > 0 ? env : undefined;
2767
2816
  }
2817
+ function applyGitHubCredentialHelperEnv(env) {
2818
+ env.GIT_TERMINAL_PROMPT = "0";
2819
+ env.GIT_CONFIG_COUNT = "2";
2820
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
2821
+ env.GIT_CONFIG_VALUE_0 = "";
2822
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
2823
+ 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';
2824
+ }
2768
2825
  function loadPersistedRuntimeSecrets(runtimeRoot) {
2769
2826
  if (!runtimeRoot) {
2770
2827
  return {};
@@ -1198,8 +1198,8 @@ function githubStatusFor(issue) {
1198
1198
  return "open";
1199
1199
  }
1200
1200
  function selectedGitHubEnv() {
1201
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1202
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1201
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1202
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1203
1203
  }
1204
1204
  function ghSpawnOptions() {
1205
1205
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -5983,12 +5983,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
5983
5983
  "task-result.json",
5984
5984
  "validation-summary.json"
5985
5985
  ]);
5986
- function resolveHostRigBinDir(root) {
5987
- return resolve24(root, ".rig", "bin");
5988
- }
5989
5986
  function isRuntimeGatewayGitPath(candidate) {
5990
5987
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
5991
5988
  }
5989
+ function isRuntimeGatewayGhPath(candidate) {
5990
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
5991
+ }
5992
5992
  function resolveOptionalMonorepoRoot(projectRoot) {
5993
5993
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
5994
5994
  if (runtimeWorkspace && existsSync21(resolve24(runtimeWorkspace, ".git"))) {
@@ -6023,6 +6023,9 @@ function resolveGitBinary(projectRoot) {
6023
6023
  }
6024
6024
  return "git";
6025
6025
  }
6026
+ function escapeRegExp2(value) {
6027
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6028
+ }
6026
6029
  function safeCurrentTaskId(projectRoot) {
6027
6030
  try {
6028
6031
  const taskId = currentTaskId(projectRoot);
@@ -6181,17 +6184,15 @@ function gitOpenPr(options) {
6181
6184
  const target = options.target || (taskId ? "monorepo" : "project");
6182
6185
  let repoRoot = options.projectRoot;
6183
6186
  let repoLabel = "project-rig";
6184
- let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
6187
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
6185
6188
  if (target === "monorepo") {
6186
6189
  repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
6187
6190
  repoLabel = "monorepo";
6188
- defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
6189
6191
  if (taskId) {
6190
6192
  gitSyncBranch(options.projectRoot, taskId, "monorepo");
6191
6193
  }
6192
6194
  } else if (taskId) {
6193
6195
  gitSyncBranch(options.projectRoot, taskId, "project");
6194
- defaultBase = inferProjectBase(options.projectRoot, defaultBase);
6195
6196
  }
6196
6197
  if (!existsSync21(resolve24(repoRoot, ".git"))) {
6197
6198
  throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
@@ -6200,9 +6201,9 @@ function gitOpenPr(options) {
6200
6201
  if (!branch || branch === "HEAD") {
6201
6202
  throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
6202
6203
  }
6203
- const base = options.base || defaultBase;
6204
6204
  const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
6205
6205
  const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
6206
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
6206
6207
  refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
6207
6208
  let reviewer = (options.reviewer || "").trim();
6208
6209
  let reviewerSource = reviewer ? "flag" : undefined;
@@ -6238,6 +6239,7 @@ function gitOpenPr(options) {
6238
6239
  "",
6239
6240
  "## Task",
6240
6241
  `- beads: ${taskId || "n/a"}`,
6242
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
6241
6243
  "",
6242
6244
  "## Review",
6243
6245
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -6329,6 +6331,29 @@ function gitOpenPr(options) {
6329
6331
  }
6330
6332
  return result;
6331
6333
  }
6334
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
6335
+ const lines = [];
6336
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
6337
+ if (runId) {
6338
+ lines.push(`- Run: ${runId}`);
6339
+ }
6340
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
6341
+ if (closeout) {
6342
+ lines.push(`- ${closeout}`);
6343
+ }
6344
+ return lines;
6345
+ }
6346
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
6347
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
6348
+ if (sourceIssueId) {
6349
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
6350
+ if (match) {
6351
+ const [, sourceRepo, issueNumber] = match;
6352
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
6353
+ }
6354
+ }
6355
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
6356
+ }
6332
6357
  function readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl) {
6333
6358
  const view = runCapture2(withGhRepo([
6334
6359
  gh,
@@ -6479,32 +6504,19 @@ function resolveGithubCliBinary(projectRoot) {
6479
6504
  if (explicit) {
6480
6505
  candidates.add(explicit);
6481
6506
  }
6507
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6508
+ candidates.add(candidate);
6509
+ }
6482
6510
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
6483
6511
  for (const entry of explicitPathEntries) {
6484
6512
  candidates.add(resolve24(entry, "gh"));
6485
6513
  }
6486
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
6487
- if (hostProjectRoot) {
6488
- candidates.add(resolve24(resolveHostRigBinDir(hostProjectRoot), "gh"));
6489
- }
6490
- candidates.add(resolve24(resolveHostRigBinDir(projectRoot), "gh"));
6491
- const runtimeContext = loadRuntimeContextFromEnv();
6492
- if (runtimeContext?.binDir) {
6493
- candidates.add(resolve24(runtimeContext.binDir, "gh"));
6494
- }
6495
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
6496
- if (runtimeHome) {
6497
- candidates.add(resolve24(runtimeHome, "bin", "gh"));
6498
- }
6499
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6500
- candidates.add(candidate);
6501
- }
6502
6514
  const bunResolved = Bun.which("gh");
6503
6515
  if (bunResolved) {
6504
6516
  candidates.add(bunResolved);
6505
6517
  }
6506
6518
  for (const candidate of candidates) {
6507
- if (candidate && existsSync21(candidate)) {
6519
+ if (candidate && existsSync21(candidate) && !isRuntimeGatewayGhPath(candidate)) {
6508
6520
  return candidate;
6509
6521
  }
6510
6522
  }
@@ -6653,6 +6665,32 @@ function withGhRepo(command, repoNameWithOwner) {
6653
6665
  }
6654
6666
  return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
6655
6667
  }
6668
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
6669
+ const remote = remoteName || "origin";
6670
+ const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
6671
+ if (symbolic.exitCode === 0) {
6672
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp2(remote)}/`), "");
6673
+ if (ref && ref !== "HEAD") {
6674
+ return ref;
6675
+ }
6676
+ }
6677
+ const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
6678
+ if (lsRemote.exitCode === 0) {
6679
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
6680
+ if (match?.[1]) {
6681
+ return match[1];
6682
+ }
6683
+ }
6684
+ const gh = resolveGithubCliBinary(projectRoot);
6685
+ if (gh && repoNameWithOwner) {
6686
+ const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
6687
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
6688
+ if (branch) {
6689
+ return branch;
6690
+ }
6691
+ }
6692
+ return fallback;
6693
+ }
6656
6694
  function inferProjectBase(projectRoot, fallback) {
6657
6695
  const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
6658
6696
  if (containing.exitCode !== 0) {
@@ -7034,6 +7072,10 @@ function runtimeGitEnv(projectRoot) {
7034
7072
  }
7035
7073
  env[key] = value;
7036
7074
  }
7075
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
7076
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
7077
+ env.GITHUB_TOKEN = rigGithubToken;
7078
+ }
7037
7079
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
7038
7080
  env.GITHUB_TOKEN = env.GH_TOKEN;
7039
7081
  }
@@ -7057,6 +7099,13 @@ function runtimeGitEnv(projectRoot) {
7057
7099
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
7058
7100
  env.GH_TOKEN = env.GITHUB_TOKEN;
7059
7101
  }
7102
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
7103
+ if (gitHubToken) {
7104
+ env.RIG_GITHUB_TOKEN = gitHubToken;
7105
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
7106
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
7107
+ applyGitHubCredentialHelperEnv(env);
7108
+ }
7060
7109
  if (runtimeKnownHosts && existsSync21(runtimeKnownHosts)) {
7061
7110
  const sshParts = [
7062
7111
  "ssh",
@@ -7073,6 +7122,14 @@ function runtimeGitEnv(projectRoot) {
7073
7122
  }
7074
7123
  return Object.keys(env).length > 0 ? env : undefined;
7075
7124
  }
7125
+ function applyGitHubCredentialHelperEnv(env) {
7126
+ env.GIT_TERMINAL_PROMPT = "0";
7127
+ env.GIT_CONFIG_COUNT = "2";
7128
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
7129
+ env.GIT_CONFIG_VALUE_0 = "";
7130
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
7131
+ 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';
7132
+ }
7076
7133
  function loadPersistedRuntimeSecrets(runtimeRoot) {
7077
7134
  if (!runtimeRoot) {
7078
7135
  return {};
@@ -104,6 +104,44 @@ function parsePrChecks(value) {
104
104
  }];
105
105
  });
106
106
  }
107
+ function parsePrViewStatusCheckRollup(value) {
108
+ if (!value?.trim())
109
+ return [];
110
+ try {
111
+ const parsed = JSON.parse(value);
112
+ const rollup = Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : [];
113
+ return rollup.flatMap((entry) => {
114
+ if (!entry || typeof entry !== "object")
115
+ return [];
116
+ const record = entry;
117
+ const name = typeof record.name === "string" ? record.name : "";
118
+ if (!name.trim())
119
+ return [];
120
+ return [{
121
+ name,
122
+ status: typeof record.status === "string" ? record.status : null,
123
+ state: typeof record.state === "string" ? record.state : null,
124
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
125
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
126
+ }];
127
+ });
128
+ } catch {
129
+ return [];
130
+ }
131
+ }
132
+ async function readPrChecks(input) {
133
+ const checks = await input.command(["pr", "checks", input.prUrl, "--json", "name,state,link"], input.cwd ? { cwd: input.cwd } : undefined);
134
+ if (checks.exitCode === 0) {
135
+ return parsePrChecks(checks.stdout);
136
+ }
137
+ const combined = `${checks.stderr ?? ""}
138
+ ${checks.stdout ?? ""}`;
139
+ if (!/unknown flag.*--json|unknown flag: --json|unknown shorthand flag/i.test(combined)) {
140
+ throw new Error(`gh pr checks ${input.prUrl} --json name,state,link failed (${checks.exitCode}): ${checks.stderr ?? checks.stdout ?? ""}`.trim());
141
+ }
142
+ const view = await runChecked(input.command, ["pr", "view", input.prUrl, "--json", "statusCheckRollup"], input.cwd, "gh");
143
+ return parsePrViewStatusCheckRollup(view.stdout);
144
+ }
107
145
  function parsePrViewReviewThreads(value) {
108
146
  if (!value?.trim())
109
147
  return [];
@@ -164,6 +202,30 @@ function normalizePrUrl(stdout) {
164
202
  throw new Error("gh pr create did not return a PR URL");
165
203
  return url;
166
204
  }
205
+ async function ensureExistingPrBodyHasRigMarkers(input) {
206
+ const view = await input.command(["pr", "view", input.prUrl, "--json", "body"], input.cwd ? { cwd: input.cwd } : undefined);
207
+ if (view.exitCode !== 0) {
208
+ throw new Error(`gh pr view ${input.prUrl} --json body failed (${view.exitCode}): ${view.stderr ?? view.stdout ?? ""}`.trim());
209
+ }
210
+ let currentBody = "";
211
+ try {
212
+ const parsed = JSON.parse(view.stdout ?? "{}");
213
+ currentBody = typeof parsed.body === "string" ? parsed.body : "";
214
+ } catch {
215
+ currentBody = "";
216
+ }
217
+ const requiredBlocks = input.body.split(/\n{2,}/).map((block) => block.trim()).filter((block) => /^Run: /i.test(block) || /^Closes #\d+/i.test(block));
218
+ const missing = requiredBlocks.filter((block) => !currentBody.includes(block));
219
+ if (missing.length === 0)
220
+ return;
221
+ const nextBody = [currentBody.trim(), ...missing].filter(Boolean).join(`
222
+
223
+ `);
224
+ const edit = await input.command(["pr", "edit", input.prUrl, "--body", nextBody], input.cwd ? { cwd: input.cwd } : undefined);
225
+ if (edit.exitCode !== 0) {
226
+ throw new Error(`gh pr edit ${input.prUrl} --body failed (${edit.exitCode}): ${edit.stderr ?? edit.stdout ?? ""}`.trim());
227
+ }
228
+ }
167
229
  async function runChecked(command, args, cwd, label = "gh") {
168
230
  const result = await command(args, cwd ? { cwd } : undefined);
169
231
  if (result.exitCode !== 0) {
@@ -188,27 +250,33 @@ function statusPathFromShortLine(line) {
188
250
  }
189
251
  return renamedPath;
190
252
  }
253
+ function isRuntimeCommitExcludedPath(path) {
254
+ const normalized = path.replace(/^\.\/+/, "").replace(/\/+$/, "");
255
+ return RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`));
256
+ }
257
+ function committableRunChangePaths(statusText) {
258
+ const seen = new Set;
259
+ const paths = [];
260
+ for (const line of statusText.split(/\r?\n/)) {
261
+ const path = statusPathFromShortLine(line);
262
+ if (!path || isRuntimeCommitExcludedPath(path) || seen.has(path))
263
+ continue;
264
+ seen.add(path);
265
+ paths.push(path);
266
+ }
267
+ return paths;
268
+ }
191
269
  function hasCommittableRunChanges(statusText) {
192
- return statusText.split(/\r?\n/).map((line) => statusPathFromShortLine(line)).filter(Boolean).some((path) => !RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => path === excluded || path.startsWith(`${excluded}/`)));
270
+ return committableRunChangePaths(statusText).length > 0;
193
271
  }
194
272
  async function commitRunChanges(input) {
195
- const status = await runChecked(input.command, ["status", "--short"], input.cwd, "git");
273
+ const status = await runChecked(input.command, ["status", "--short", "--untracked-files=all"], input.cwd, "git");
196
274
  const statusText = status.stdout ?? "";
197
- if (!statusText.trim() || !hasCommittableRunChanges(statusText)) {
275
+ const committablePaths = committableRunChangePaths(statusText);
276
+ if (!statusText.trim() || committablePaths.length === 0) {
198
277
  return { committed: false, status: statusText };
199
278
  }
200
- await runChecked(input.command, [
201
- "add",
202
- "-A",
203
- "--",
204
- ".",
205
- ":(exclude).rig",
206
- ":(exclude).rig/**",
207
- ":(exclude)artifacts",
208
- ":(exclude)artifacts/**",
209
- ":(exclude)node_modules",
210
- ":(exclude)node_modules/**"
211
- ], input.cwd, "git");
279
+ await runChecked(input.command, ["add", "-A", "--", ...committablePaths], input.cwd, "git");
212
280
  const staged = await input.command(["diff", "--cached", "--quiet"], { cwd: input.cwd });
213
281
  if (staged.exitCode === 0) {
214
282
  return { committed: false, status: statusText };
@@ -314,12 +382,15 @@ ${createResult.stdout ?? ""}`) : null;
314
382
  throw new Error(`gh ${createArgs.join(" ")} failed (${createResult.exitCode}): ${createResult.stderr ?? createResult.stdout ?? ""}`.trim());
315
383
  }
316
384
  const prUrl = existingPrUrl ?? normalizePrUrl(createResult.stdout);
385
+ if (existingPrUrl) {
386
+ await ensureExistingPrBodyHasRigMarkers({ prUrl, body, command: input.command, cwd: input.projectRoot });
387
+ }
317
388
  await input.lifecycle?.onPrOpened?.({ prUrl });
318
389
  const { maxPrFixIterations } = resolvePrAutomationLimits(input.config);
319
390
  let latestFeedback = [];
320
391
  for (let iteration = 1;iteration <= maxPrFixIterations; iteration += 1) {
321
392
  await input.lifecycle?.onReviewCiStarted?.({ prUrl, iteration });
322
- const checks = prConfig.watchChecks === false ? [] : parsePrChecks((await runChecked(input.command, ["pr", "checks", prUrl, "--json", "name,state,link"], input.projectRoot)).stdout);
393
+ const checks = prConfig.watchChecks === false ? [] : await readPrChecks({ prUrl, command: input.command, cwd: input.projectRoot });
323
394
  const reviewThreads = prConfig.autoFixReview === false ? [] : parsePrViewReviewThreads((await runChecked(input.command, ["pr", "view", prUrl, "--json", "reviewDecision,reviews"], input.projectRoot)).stdout);
324
395
  latestFeedback = collectActionablePrFeedback({
325
396
  checks,
@@ -2713,11 +2713,13 @@ function runStatus(projectRoot, runtimeContext) {
2713
2713
  recentRuns: runs.slice(0, 10)
2714
2714
  };
2715
2715
  }
2716
- async function runResume(projectRoot, runtimeContext) {
2717
- const resumable = listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run2) => Boolean(run2)).filter((run2) => run2.mode === "local" && (run2.status === "stopped" || run2.status === "failed" || run2.status === "created")).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
2718
- const run = resumable[0];
2716
+ function latestLocalRunForResume(projectRoot) {
2717
+ return listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.mode === "local" && ["created", "preparing", "running", "validating", "reviewing", "stopped", "failed", "needs_attention"].includes(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0] ?? null;
2718
+ }
2719
+ async function submitRunResumeRequest(projectRoot, input) {
2720
+ const run = latestLocalRunForResume(projectRoot);
2719
2721
  if (!run) {
2720
- throw new RemoteCliError("RIG_RUN_NOTHING_TO_RESUME", "No stopped local run is available to resume.", 2);
2722
+ throw new RemoteCliError(input.failureCode, input.nothingMessage, 2);
2721
2723
  }
2722
2724
  const server = await ensureServerForRuns(projectRoot);
2723
2725
  const response = await fetch(`${server.baseUrl}/api/runs/resume`, {
@@ -2726,14 +2728,28 @@ async function runResume(projectRoot, runtimeContext) {
2726
2728
  "content-type": "application/json",
2727
2729
  ...server.authToken ? { authorization: `Bearer ${server.authToken}` } : {}
2728
2730
  },
2729
- body: JSON.stringify({ runId: run.runId, createdAt: new Date().toISOString() })
2731
+ body: JSON.stringify({ runId: run.runId, createdAt: new Date().toISOString(), restart: input.restart })
2730
2732
  });
2731
2733
  if (!response.ok) {
2732
2734
  const text = await response.text();
2733
- throw new RemoteCliError("RIG_RUN_RESUME_FAILED", text || response.statusText, 1, { runId: run.runId });
2735
+ throw new RemoteCliError(input.restart ? "RIG_RUN_RESTART_FAILED" : "RIG_RUN_RESUME_FAILED", text || response.statusText, 1, { runId: run.runId });
2734
2736
  }
2735
2737
  return { runId: run.runId };
2736
2738
  }
2739
+ async function runResume(projectRoot, runtimeContext) {
2740
+ return submitRunResumeRequest(projectRoot, {
2741
+ restart: false,
2742
+ failureCode: "RIG_RUN_NOTHING_TO_RESUME",
2743
+ nothingMessage: "No resumable local run is available."
2744
+ });
2745
+ }
2746
+ async function runRestart(projectRoot, runtimeContext) {
2747
+ return submitRunResumeRequest(projectRoot, {
2748
+ restart: true,
2749
+ failureCode: "RIG_RUN_NOTHING_TO_RESTART",
2750
+ nothingMessage: "No local run is available to restart."
2751
+ });
2752
+ }
2737
2753
  async function runStop(projectRoot) {
2738
2754
  const activeRuns = listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.mode === "local" && ACTIVE_RUN_STATUSES.has(String(run.status ?? "")));
2739
2755
  if (activeRuns.length === 0) {
@@ -3267,6 +3283,7 @@ export {
3267
3283
  runStop,
3268
3284
  runStatus,
3269
3285
  runResume,
3286
+ runRestart,
3270
3287
  resolvePreferredShellBinary,
3271
3288
  resolveLocalControlBinarySourceRoot,
3272
3289
  resolveDefaultEpic,
@@ -1089,8 +1089,8 @@ function githubStatusFor(issue) {
1089
1089
  return "open";
1090
1090
  }
1091
1091
  function selectedGitHubEnv() {
1092
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1093
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1092
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1093
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1094
1094
  }
1095
1095
  function ghSpawnOptions() {
1096
1096
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -1354,8 +1354,8 @@ function githubStatusFor(issue) {
1354
1354
  return "open";
1355
1355
  }
1356
1356
  function selectedGitHubEnv() {
1357
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1358
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1357
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1358
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1359
1359
  }
1360
1360
  function ghSpawnOptions() {
1361
1361
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -977,8 +977,8 @@ function githubStatusFor(issue) {
977
977
  return "open";
978
978
  }
979
979
  function selectedGitHubEnv() {
980
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
981
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
980
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
981
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
982
982
  }
983
983
  function ghSpawnOptions() {
984
984
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };