@h-rig/runtime 0.0.6-alpha.3 → 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.
@@ -2645,8 +2645,8 @@ function ensureStatusLabel(bin, repo, spawnFn, label) {
2645
2645
  }
2646
2646
  }
2647
2647
  function selectedGitHubEnv() {
2648
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
2649
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
2648
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
2649
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
2650
2650
  }
2651
2651
  function ghSpawnOptions() {
2652
2652
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -5993,12 +5993,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
5993
5993
  "task-result.json",
5994
5994
  "validation-summary.json"
5995
5995
  ]);
5996
- function resolveHostRigBinDir(root) {
5997
- return resolve23(root, ".rig", "bin");
5998
- }
5999
5996
  function isRuntimeGatewayGitPath(candidate) {
6000
5997
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
6001
5998
  }
5999
+ function isRuntimeGatewayGhPath(candidate) {
6000
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
6001
+ }
6002
6002
  function resolveOptionalMonorepoRoot(projectRoot) {
6003
6003
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
6004
6004
  if (runtimeWorkspace && existsSync20(resolve23(runtimeWorkspace, ".git"))) {
@@ -6033,6 +6033,9 @@ function resolveGitBinary(projectRoot) {
6033
6033
  }
6034
6034
  return "git";
6035
6035
  }
6036
+ function escapeRegExp(value) {
6037
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6038
+ }
6036
6039
  function safeCurrentTaskId(projectRoot) {
6037
6040
  try {
6038
6041
  const taskId = currentTaskId(projectRoot);
@@ -6156,6 +6159,7 @@ function gitOpenPr(options) {
6156
6159
  "",
6157
6160
  "## Task",
6158
6161
  `- beads: ${taskId || "n/a"}`,
6162
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
6159
6163
  "",
6160
6164
  "## Review",
6161
6165
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -6247,6 +6251,29 @@ function gitOpenPr(options) {
6247
6251
  }
6248
6252
  return result;
6249
6253
  }
6254
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
6255
+ const lines = [];
6256
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
6257
+ if (runId) {
6258
+ lines.push(`- Run: ${runId}`);
6259
+ }
6260
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
6261
+ if (closeout) {
6262
+ lines.push(`- ${closeout}`);
6263
+ }
6264
+ return lines;
6265
+ }
6266
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
6267
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
6268
+ if (sourceIssueId) {
6269
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
6270
+ if (match) {
6271
+ const [, sourceRepo, issueNumber] = match;
6272
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
6273
+ }
6274
+ }
6275
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
6276
+ }
6250
6277
  function resolveTaskBranchRef(projectRoot, taskId) {
6251
6278
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
6252
6279
  }
@@ -6493,32 +6520,19 @@ function resolveGithubCliBinary(projectRoot) {
6493
6520
  if (explicit) {
6494
6521
  candidates.add(explicit);
6495
6522
  }
6523
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6524
+ candidates.add(candidate);
6525
+ }
6496
6526
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
6497
6527
  for (const entry of explicitPathEntries) {
6498
6528
  candidates.add(resolve23(entry, "gh"));
6499
6529
  }
6500
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
6501
- if (hostProjectRoot) {
6502
- candidates.add(resolve23(resolveHostRigBinDir(hostProjectRoot), "gh"));
6503
- }
6504
- candidates.add(resolve23(resolveHostRigBinDir(projectRoot), "gh"));
6505
- const runtimeContext = loadRuntimeContextFromEnv();
6506
- if (runtimeContext?.binDir) {
6507
- candidates.add(resolve23(runtimeContext.binDir, "gh"));
6508
- }
6509
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
6510
- if (runtimeHome) {
6511
- candidates.add(resolve23(runtimeHome, "bin", "gh"));
6512
- }
6513
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6514
- candidates.add(candidate);
6515
- }
6516
6530
  const bunResolved = Bun.which("gh");
6517
6531
  if (bunResolved) {
6518
6532
  candidates.add(bunResolved);
6519
6533
  }
6520
6534
  for (const candidate of candidates) {
6521
- if (candidate && existsSync20(candidate)) {
6535
+ if (candidate && existsSync20(candidate) && !isRuntimeGatewayGhPath(candidate)) {
6522
6536
  return candidate;
6523
6537
  }
6524
6538
  }
@@ -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() } };
@@ -7622,7 +7622,11 @@ async function ensureAgentRuntime(options) {
7622
7622
  mkdirSync21(runtime.binDir, { recursive: true });
7623
7623
  mkdirSync21(workspaceLayout.distDir, { recursive: true });
7624
7624
  prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
7625
- await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
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
+ }
7626
7630
  const ctx = {
7627
7631
  runtimeId: options.id,
7628
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() } };
@@ -7622,7 +7622,11 @@ async function ensureAgentRuntime(options) {
7622
7622
  mkdirSync21(runtime.binDir, { recursive: true });
7623
7623
  mkdirSync21(workspaceLayout.distDir, { recursive: true });
7624
7624
  prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
7625
- await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
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
+ }
7626
7630
  const ctx = {
7627
7631
  runtimeId: options.id,
7628
7632
  taskId: options.taskId,
@@ -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);
@@ -1828,6 +1831,7 @@ function gitOpenPr(options) {
1828
1831
  "",
1829
1832
  "## Task",
1830
1833
  `- beads: ${taskId || "n/a"}`,
1834
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
1831
1835
  "",
1832
1836
  "## Review",
1833
1837
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -1919,6 +1923,29 @@ function gitOpenPr(options) {
1919
1923
  }
1920
1924
  return result;
1921
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
+ }
1922
1949
  function resolveTaskBranchRef(projectRoot, taskId) {
1923
1950
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1924
1951
  }
@@ -2169,32 +2196,19 @@ function resolveGithubCliBinary(projectRoot) {
2169
2196
  if (explicit) {
2170
2197
  candidates.add(explicit);
2171
2198
  }
2199
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
2200
+ candidates.add(candidate);
2201
+ }
2172
2202
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
2173
2203
  for (const entry of explicitPathEntries) {
2174
2204
  candidates.add(resolve11(entry, "gh"));
2175
2205
  }
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
2206
  const bunResolved = Bun.which("gh");
2193
2207
  if (bunResolved) {
2194
2208
  candidates.add(bunResolved);
2195
2209
  }
2196
2210
  for (const candidate of candidates) {
2197
- if (candidate && existsSync9(candidate)) {
2211
+ if (candidate && existsSync9(candidate) && !isRuntimeGatewayGhPath(candidate)) {
2198
2212
  return candidate;
2199
2213
  }
2200
2214
  }
@@ -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() } };
@@ -5455,7 +5455,7 @@ This file records approaches that did not work.
5455
5455
  `, "utf-8");
5456
5456
  }
5457
5457
  const content = readFileSync10(failedPath, "utf-8");
5458
- const attempts = (content.match(new RegExp(`^## ${escapeRegExp2(activeTask)}\\b`, "gm")) || []).length + 1;
5458
+ const attempts = (content.match(new RegExp(`^## ${escapeRegExp(activeTask)}\\b`, "gm")) || []).length + 1;
5459
5459
  appendFileSync(failedPath, `
5460
5460
  ## ${activeTask} - Attempt ${attempts} (${nowIso()})
5461
5461
 
@@ -5954,7 +5954,7 @@ function printArtifactSection(path, header) {
5954
5954
  process.stdout.write(readFileSync10(path, "utf-8"));
5955
5955
  console.log("");
5956
5956
  }
5957
- function escapeRegExp2(value) {
5957
+ function escapeRegExp(value) {
5958
5958
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5959
5959
  }
5960
5960
  function changedFilesForTask(projectRoot, taskId, scoped) {
@@ -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);
@@ -6236,6 +6239,7 @@ function gitOpenPr(options) {
6236
6239
  "",
6237
6240
  "## Task",
6238
6241
  `- beads: ${taskId || "n/a"}`,
6242
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
6239
6243
  "",
6240
6244
  "## Review",
6241
6245
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -6327,6 +6331,29 @@ function gitOpenPr(options) {
6327
6331
  }
6328
6332
  return result;
6329
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
+ }
6330
6357
  function readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl) {
6331
6358
  const view = runCapture2(withGhRepo([
6332
6359
  gh,
@@ -6477,32 +6504,19 @@ function resolveGithubCliBinary(projectRoot) {
6477
6504
  if (explicit) {
6478
6505
  candidates.add(explicit);
6479
6506
  }
6507
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6508
+ candidates.add(candidate);
6509
+ }
6480
6510
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
6481
6511
  for (const entry of explicitPathEntries) {
6482
6512
  candidates.add(resolve24(entry, "gh"));
6483
6513
  }
6484
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
6485
- if (hostProjectRoot) {
6486
- candidates.add(resolve24(resolveHostRigBinDir(hostProjectRoot), "gh"));
6487
- }
6488
- candidates.add(resolve24(resolveHostRigBinDir(projectRoot), "gh"));
6489
- const runtimeContext = loadRuntimeContextFromEnv();
6490
- if (runtimeContext?.binDir) {
6491
- candidates.add(resolve24(runtimeContext.binDir, "gh"));
6492
- }
6493
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
6494
- if (runtimeHome) {
6495
- candidates.add(resolve24(runtimeHome, "bin", "gh"));
6496
- }
6497
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6498
- candidates.add(candidate);
6499
- }
6500
6514
  const bunResolved = Bun.which("gh");
6501
6515
  if (bunResolved) {
6502
6516
  candidates.add(bunResolved);
6503
6517
  }
6504
6518
  for (const candidate of candidates) {
6505
- if (candidate && existsSync21(candidate)) {
6519
+ if (candidate && existsSync21(candidate) && !isRuntimeGatewayGhPath(candidate)) {
6506
6520
  return candidate;
6507
6521
  }
6508
6522
  }
@@ -6655,7 +6669,7 @@ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, re
6655
6669
  const remote = remoteName || "origin";
6656
6670
  const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
6657
6671
  if (symbolic.exitCode === 0) {
6658
- const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
6672
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp2(remote)}/`), "");
6659
6673
  if (ref && ref !== "HEAD") {
6660
6674
  return ref;
6661
6675
  }
@@ -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() } };
@@ -5634,7 +5634,7 @@ This file records approaches that did not work.
5634
5634
  `, "utf-8");
5635
5635
  }
5636
5636
  const content = readFileSync10(failedPath, "utf-8");
5637
- const attempts = (content.match(new RegExp(`^## ${escapeRegExp2(activeTask)}\\b`, "gm")) || []).length + 1;
5637
+ const attempts = (content.match(new RegExp(`^## ${escapeRegExp(activeTask)}\\b`, "gm")) || []).length + 1;
5638
5638
  appendFileSync(failedPath, `
5639
5639
  ## ${activeTask} - Attempt ${attempts} (${nowIso()})
5640
5640
 
@@ -6290,7 +6290,7 @@ function printArtifactSection(path, header) {
6290
6290
  process.stdout.write(readFileSync10(path, "utf-8"));
6291
6291
  console.log("");
6292
6292
  }
6293
- function escapeRegExp2(value) {
6293
+ function escapeRegExp(value) {
6294
6294
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6295
6295
  }
6296
6296
  function changedFilesForTask(projectRoot, taskId, scoped) {
@@ -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() } };